]> git.lizzy.rs Git - micro.git/blob - cmd/micro/util.go
Fix autocomplete behavior for empty args
[micro.git] / cmd / micro / util.go
1 package main
2
3 import (
4         "os"
5         "path/filepath"
6         "reflect"
7         "runtime"
8         "strconv"
9         "strings"
10         "time"
11         "unicode/utf8"
12
13         "github.com/mattn/go-runewidth"
14         homedir "github.com/mitchellh/go-homedir"
15 )
16
17 // Util.go is a collection of utility functions that are used throughout
18 // the program
19
20 // Count returns the length of a string in runes
21 // This is exactly equivalent to utf8.RuneCountInString(), just less characters
22 func Count(s string) int {
23         return utf8.RuneCountInString(s)
24 }
25
26 // NumOccurrences counts the number of occurrences of a byte in a string
27 func NumOccurrences(s string, c byte) int {
28         var n int
29         for i := 0; i < len(s); i++ {
30                 if s[i] == c {
31                         n++
32                 }
33         }
34         return n
35 }
36
37 // Spaces returns a string with n spaces
38 func Spaces(n int) string {
39         return strings.Repeat(" ", n)
40 }
41
42 // Min takes the min of two ints
43 func Min(a, b int) int {
44         if a > b {
45                 return b
46         }
47         return a
48 }
49
50 // Max takes the max of two ints
51 func Max(a, b int) int {
52         if a > b {
53                 return a
54         }
55         return b
56 }
57
58 // FSize gets the size of a file
59 func FSize(f *os.File) int64 {
60         fi, _ := f.Stat()
61         // get the size
62         return fi.Size()
63 }
64
65 // IsWordChar returns whether or not the string is a 'word character'
66 // If it is a unicode character, then it does not match
67 // Word characters are defined as [A-Za-z0-9_]
68 func IsWordChar(str string) bool {
69         if len(str) > 1 {
70                 // Unicode
71                 return true
72         }
73         c := str[0]
74         return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
75 }
76
77 // IsWhitespace returns true if the given rune is a space, tab, or newline
78 func IsWhitespace(c rune) bool {
79         return c == ' ' || c == '\t' || c == '\n'
80 }
81
82 // IsStrWhitespace returns true if the given string is all whitespace
83 func IsStrWhitespace(str string) bool {
84         for _, c := range str {
85                 if !IsWhitespace(c) {
86                         return false
87                 }
88         }
89         return true
90 }
91
92 // Contains returns whether or not a string array contains a given string
93 func Contains(list []string, a string) bool {
94         for _, b := range list {
95                 if b == a {
96                         return true
97                 }
98         }
99         return false
100 }
101
102 // Insert makes a simple insert into a string at the given position
103 func Insert(str string, pos int, value string) string {
104         return string([]rune(str)[:pos]) + value + string([]rune(str)[pos:])
105 }
106
107 // MakeRelative will attempt to make a relative path between path and base
108 func MakeRelative(path, base string) (string, error) {
109         if len(path) > 0 {
110                 rel, err := filepath.Rel(base, path)
111                 if err != nil {
112                         return path, err
113                 }
114                 return rel, nil
115         }
116         return path, nil
117 }
118
119 // GetLeadingWhitespace returns the leading whitespace of the given string
120 func GetLeadingWhitespace(str string) string {
121         ws := ""
122         for _, c := range str {
123                 if c == ' ' || c == '\t' {
124                         ws += string(c)
125                 } else {
126                         break
127                 }
128         }
129         return ws
130 }
131
132 // IsSpaces checks if a given string is only spaces
133 func IsSpaces(str string) bool {
134         for _, c := range str {
135                 if c != ' ' {
136                         return false
137                 }
138         }
139
140         return true
141 }
142
143 // IsSpacesOrTabs checks if a given string contains only spaces and tabs
144 func IsSpacesOrTabs(str string) bool {
145         for _, c := range str {
146                 if c != ' ' && c != '\t' {
147                         return false
148                 }
149         }
150
151         return true
152 }
153
154 // ParseBool is almost exactly like strconv.ParseBool, except it also accepts 'on' and 'off'
155 // as 'true' and 'false' respectively
156 func ParseBool(str string) (bool, error) {
157         if str == "on" {
158                 return true, nil
159         }
160         if str == "off" {
161                 return false, nil
162         }
163         return strconv.ParseBool(str)
164 }
165
166 // EscapePath replaces every path separator in a given path with a %
167 func EscapePath(path string) string {
168         path = filepath.ToSlash(path)
169         return strings.Replace(path, "/", "%", -1)
170 }
171
172 // GetModTime returns the last modification time for a given file
173 // It also returns a boolean if there was a problem accessing the file
174 func GetModTime(path string) (time.Time, bool) {
175         info, err := os.Stat(path)
176         if err != nil {
177                 return time.Now(), false
178         }
179         return info.ModTime(), true
180 }
181
182 // StringWidth returns the width of a string where tabs count as `tabsize` width
183 func StringWidth(str string, tabsize int) int {
184         sw := runewidth.StringWidth(str)
185         lineIdx := 0
186         for _, ch := range str {
187                 switch ch {
188                 case '\t':
189                         ts := tabsize - (lineIdx % tabsize)
190                         sw += ts
191                         lineIdx += ts
192                 case '\n':
193                         lineIdx = 0
194                 default:
195                         lineIdx++
196                 }
197         }
198         return sw
199 }
200
201 // WidthOfLargeRunes searches all the runes in a string and counts up all the widths of runes
202 // that have a width larger than 1 (this also counts tabs as `tabsize` width)
203 func WidthOfLargeRunes(str string, tabsize int) int {
204         count := 0
205         lineIdx := 0
206         for _, ch := range str {
207                 var w int
208                 if ch == '\t' {
209                         w = tabsize - (lineIdx % tabsize)
210                 } else {
211                         w = runewidth.RuneWidth(ch)
212                 }
213                 if w > 1 {
214                         count += (w - 1)
215                 }
216                 if ch == '\n' {
217                         lineIdx = 0
218                 } else {
219                         lineIdx += w
220                 }
221         }
222         return count
223 }
224
225 // RunePos returns the rune index of a given byte index
226 // This could cause problems if the byte index is between code points
227 func runePos(p int, str string) int {
228         return utf8.RuneCountInString(str[:p])
229 }
230
231 func lcs(a, b string) string {
232         arunes := []rune(a)
233         brunes := []rune(b)
234
235         lcs := ""
236         for i, r := range arunes {
237                 if i >= len(brunes) {
238                         break
239                 }
240                 if r == brunes[i] {
241                         lcs += string(r)
242                 } else {
243                         break
244                 }
245         }
246         return lcs
247 }
248
249 // CommonSubstring gets a common substring among the inputs
250 func CommonSubstring(arr ...string) string {
251         commonStr := arr[0]
252
253         for _, str := range arr[1:] {
254                 commonStr = lcs(commonStr, str)
255         }
256
257         return commonStr
258 }
259
260 // Abs is a simple absolute value function for ints
261 func Abs(n int) int {
262         if n < 0 {
263                 return -n
264         }
265         return n
266 }
267
268 // FuncName returns the full name of a given function object
269 func FuncName(i interface{}) string {
270         return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
271 }
272
273 // ShortFuncName returns the name only of a given function object
274 func ShortFuncName(i interface{}) string {
275         return strings.TrimPrefix(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name(), "main.(*View).")
276 }
277
278 // ReplaceHome takes a path as input and replaces ~ at the start of the path with the user's
279 // home directory. Does nothing if the path does not start with '~'.
280 func ReplaceHome(path string) string {
281         if !strings.HasPrefix(path, "~") {
282                 return path
283         }
284
285         home, err := homedir.Dir()
286         if err != nil {
287                 messenger.Error("Could not find home directory: ", err)
288                 return path
289         }
290         return strings.Replace(path, "~", home, 1)
291 }