]> git.lizzy.rs Git - micro.git/blob - cmd/micro/util.go
Don't print error message if history file doesn't exist
[micro.git] / cmd / micro / util.go
1 package main
2
3 import (
4         "bytes"
5         "os"
6         "path/filepath"
7         "reflect"
8         "runtime"
9         "strconv"
10         "strings"
11         "time"
12         "unicode/utf8"
13
14         "github.com/mattn/go-runewidth"
15         homedir "github.com/mitchellh/go-homedir"
16 )
17
18 // Util.go is a collection of utility functions that are used throughout
19 // the program
20
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)
25 }
26
27 // NumOccurrences counts the number of occurrences of a byte in a string
28 func NumOccurrences(s string, c byte) int {
29         var n int
30         for i := 0; i < len(s); i++ {
31                 if s[i] == c {
32                         n++
33                 }
34         }
35         return n
36 }
37
38 // Spaces returns a string with n spaces
39 func Spaces(n int) string {
40         return strings.Repeat(" ", n)
41 }
42
43 // Min takes the min of two ints
44 func Min(a, b int) int {
45         if a > b {
46                 return b
47         }
48         return a
49 }
50
51 // Max takes the max of two ints
52 func Max(a, b int) int {
53         if a > b {
54                 return a
55         }
56         return b
57 }
58
59 // FSize gets the size of a file
60 func FSize(f *os.File) int64 {
61         fi, _ := f.Stat()
62         // get the size
63         return fi.Size()
64 }
65
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 {
70         if len(str) > 1 {
71                 // Unicode
72                 return true
73         }
74         c := str[0]
75         return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
76 }
77
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'
81 }
82
83 // IsStrWhitespace returns true if the given string is all whitespace
84 func IsStrWhitespace(str string) bool {
85         for _, c := range str {
86                 if !IsWhitespace(c) {
87                         return false
88                 }
89         }
90         return true
91 }
92
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 {
96                 if b == a {
97                         return true
98                 }
99         }
100         return false
101 }
102
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:])
106 }
107
108 // MakeRelative will attempt to make a relative path between path and base
109 func MakeRelative(path, base string) (string, error) {
110         if len(path) > 0 {
111                 rel, err := filepath.Rel(base, path)
112                 if err != nil {
113                         return path, err
114                 }
115                 return rel, nil
116         }
117         return path, nil
118 }
119
120 // GetLeadingWhitespace returns the leading whitespace of the given string
121 func GetLeadingWhitespace(str string) string {
122         ws := ""
123         for _, c := range str {
124                 if c == ' ' || c == '\t' {
125                         ws += string(c)
126                 } else {
127                         break
128                 }
129         }
130         return ws
131 }
132
133 // IsSpaces checks if a given string is only spaces
134 func IsSpaces(str string) bool {
135         for _, c := range str {
136                 if c != ' ' {
137                         return false
138                 }
139         }
140
141         return true
142 }
143
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' {
148                         return false
149                 }
150         }
151
152         return true
153 }
154
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) {
158         if str == "on" {
159                 return true, nil
160         }
161         if str == "off" {
162                 return false, nil
163         }
164         return strconv.ParseBool(str)
165 }
166
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)
171 }
172
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)
177         if err != nil {
178                 return time.Now(), false
179         }
180         return info.ModTime(), true
181 }
182
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)
186         lineIdx := 0
187         for _, ch := range str {
188                 switch ch {
189                 case '\t':
190                         ts := tabsize - (lineIdx % tabsize)
191                         sw += ts
192                         lineIdx += ts
193                 case '\n':
194                         lineIdx = 0
195                 default:
196                         lineIdx++
197                 }
198         }
199         return sw
200 }
201
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 {
205         count := 0
206         lineIdx := 0
207         for _, ch := range str {
208                 var w int
209                 if ch == '\t' {
210                         w = tabsize - (lineIdx % tabsize)
211                 } else {
212                         w = runewidth.RuneWidth(ch)
213                 }
214                 if w > 1 {
215                         count += (w - 1)
216                 }
217                 if ch == '\n' {
218                         lineIdx = 0
219                 } else {
220                         lineIdx += w
221                 }
222         }
223         return count
224 }
225
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])
230 }
231
232 func lcs(a, b string) string {
233         arunes := []rune(a)
234         brunes := []rune(b)
235
236         lcs := ""
237         for i, r := range arunes {
238                 if i >= len(brunes) {
239                         break
240                 }
241                 if r == brunes[i] {
242                         lcs += string(r)
243                 } else {
244                         break
245                 }
246         }
247         return lcs
248 }
249
250 // CommonSubstring gets a common substring among the inputs
251 func CommonSubstring(arr ...string) string {
252         commonStr := arr[0]
253
254         for _, str := range arr[1:] {
255                 commonStr = lcs(commonStr, str)
256         }
257
258         return commonStr
259 }
260
261 // Abs is a simple absolute value function for ints
262 func Abs(n int) int {
263         if n < 0 {
264                 return -n
265         }
266         return n
267 }
268
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()
272 }
273
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).")
277 }
278
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 {
282         var result []string
283         var curQuote *bytes.Buffer
284
285         curArg := new(bytes.Buffer)
286         escape := false
287
288         finishQuote := func() {
289                 if curQuote == nil {
290                         return
291                 }
292                 str := curQuote.String()
293                 if unquoted, err := strconv.Unquote(str); err == nil {
294                         str = unquoted
295                 }
296                 curArg.WriteString(str)
297                 curQuote = nil
298         }
299
300         appendResult := func() {
301                 finishQuote()
302                 escape = false
303
304                 str := curArg.String()
305                 result = append(result, str)
306                 curArg.Reset()
307         }
308
309         for _, r := range input {
310                 if r == ' ' && curQuote == nil {
311                         appendResult()
312                 } else {
313                         runeHandled := false
314                         appendRuneToBuff := func() {
315                                 if curQuote != nil {
316                                         curQuote.WriteRune(r)
317                                 } else {
318                                         curArg.WriteRune(r)
319                                 }
320                                 runeHandled = true
321                         }
322
323                         if r == '"' && curQuote == nil {
324                                 curQuote = new(bytes.Buffer)
325                                 appendRuneToBuff()
326                         } else {
327                                 if curQuote != nil && !escape {
328                                         if r == '"' {
329                                                 appendRuneToBuff()
330                                                 finishQuote()
331                                         } else if r == '\\' {
332                                                 appendRuneToBuff()
333                                                 escape = true
334                                                 continue
335                                         }
336                                 }
337                         }
338                         if !runeHandled {
339                                 appendRuneToBuff()
340                         }
341                 }
342
343                 escape = false
344         }
345         appendResult()
346         return result
347 }
348
349 // JoinCommandArgs joins multiple command arguments and quote the strings if needed.
350 func JoinCommandArgs(args ...string) string {
351         buf := new(bytes.Buffer)
352         first := true
353         for _, arg := range args {
354                 if first {
355                         first = false
356                 } else {
357                         buf.WriteRune(' ')
358                 }
359                 quoted := strconv.Quote(arg)
360                 if quoted[1:len(quoted)-1] != arg || strings.ContainsRune(arg, ' ') {
361                         buf.WriteString(quoted)
362                 } else {
363                         buf.WriteString(arg)
364                 }
365         }
366
367         return buf.String()
368 }
369
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, "~") {
374                 return path
375         }
376
377         home, err := homedir.Dir()
378         if err != nil {
379                 messenger.Error("Could not find home directory: ", err)
380                 return path
381         }
382         return strings.Replace(path, "~", home, 1)
383 }