]> git.lizzy.rs Git - micro.git/blob - cmd/micro/util.go
Revert "Improve performance for very long lines"
[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 )
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 occurences 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         var str string
40         for i := 0; i < n; i++ {
41                 str += " "
42         }
43         return str
44 }
45
46 // Min takes the min of two ints
47 func Min(a, b int) int {
48         if a > b {
49                 return b
50         }
51         return a
52 }
53
54 // Max takes the max of two ints
55 func Max(a, b int) int {
56         if a > b {
57                 return a
58         }
59         return b
60 }
61
62 // IsWordChar returns whether or not the string is a 'word character'
63 // If it is a unicode character, then it does not match
64 // Word characters are defined as [A-Za-z0-9_]
65 func IsWordChar(str string) bool {
66         if len(str) > 1 {
67                 // Unicode
68                 return true
69         }
70         c := str[0]
71         return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
72 }
73
74 // IsWhitespace returns true if the given rune is a space, tab, or newline
75 func IsWhitespace(c rune) bool {
76         return c == ' ' || c == '\t' || c == '\n'
77 }
78
79 // Contains returns whether or not a string array contains a given string
80 func Contains(list []string, a string) bool {
81         for _, b := range list {
82                 if b == a {
83                         return true
84                 }
85         }
86         return false
87 }
88
89 // Insert makes a simple insert into a string at the given position
90 func Insert(str string, pos int, value string) string {
91         return string([]rune(str)[:pos]) + value + string([]rune(str)[pos:])
92 }
93
94 // GetLeadingWhitespace returns the leading whitespace of the given string
95 func GetLeadingWhitespace(str string) string {
96         ws := ""
97         for _, c := range str {
98                 if c == ' ' || c == '\t' {
99                         ws += string(c)
100                 } else {
101                         break
102                 }
103         }
104         return ws
105 }
106
107 // IsSpaces checks if a given string is only spaces
108 func IsSpaces(str string) bool {
109         for _, c := range str {
110                 if c != ' ' {
111                         return false
112                 }
113         }
114
115         return true
116 }
117
118 // IsSpacesOrTabs checks if a given string contains only spaces and tabs
119 func IsSpacesOrTabs(str string) bool {
120         for _, c := range str {
121                 if c != ' ' && c != '\t' {
122                         return false
123                 }
124         }
125
126         return true
127 }
128
129 // ParseBool is almost exactly like strconv.ParseBool, except it also accepts 'on' and 'off'
130 // as 'true' and 'false' respectively
131 func ParseBool(str string) (bool, error) {
132         if str == "on" {
133                 return true, nil
134         }
135         if str == "off" {
136                 return false, nil
137         }
138         return strconv.ParseBool(str)
139 }
140
141 // EscapePath replaces every path separator in a given path with a %
142 func EscapePath(path string) string {
143         path = filepath.ToSlash(path)
144         return strings.Replace(path, "/", "%", -1)
145 }
146
147 // GetModTime returns the last modification time for a given file
148 // It also returns a boolean if there was a problem accessing the file
149 func GetModTime(path string) (time.Time, bool) {
150         info, err := os.Stat(path)
151         if err != nil {
152                 return time.Now(), false
153         }
154         return info.ModTime(), true
155 }
156
157 // StringWidth returns the width of a string where tabs count as `tabsize` width
158 func StringWidth(str string, tabsize int) int {
159         sw := runewidth.StringWidth(str)
160         sw += NumOccurrences(str, '\t') * (tabsize - 1)
161         return sw
162 }
163
164 // WidthOfLargeRunes searches all the runes in a string and counts up all the widths of runes
165 // that have a width larger than 1 (this also counts tabs as `tabsize` width)
166 func WidthOfLargeRunes(str string, tabsize int) int {
167         count := 0
168         for _, ch := range str {
169                 var w int
170                 if ch == '\t' {
171                         w = tabsize
172                 } else {
173                         w = runewidth.RuneWidth(ch)
174                 }
175                 if w > 1 {
176                         count += (w - 1)
177                 }
178         }
179         return count
180 }
181
182 // RunePos returns the rune index of a given byte index
183 // This could cause problems if the byte index is between code points
184 func runePos(p int, str string) int {
185         return utf8.RuneCountInString(str[:p])
186 }
187
188 func lcs(a, b string) string {
189         arunes := []rune(a)
190         brunes := []rune(b)
191
192         lcs := ""
193         for i, r := range arunes {
194                 if i >= len(brunes) {
195                         break
196                 }
197                 if r == brunes[i] {
198                         lcs += string(r)
199                 } else {
200                         break
201                 }
202         }
203         return lcs
204 }
205
206 func CommonSubstring(arr ...string) string {
207         commonStr := arr[0]
208
209         for _, str := range arr[1:] {
210                 commonStr = lcs(commonStr, str)
211         }
212
213         return commonStr
214 }
215
216 // Abs is a simple absolute value function for ints
217 func Abs(n int) int {
218         if n < 0 {
219                 return -n
220         }
221         return n
222 }
223
224 // FuncName returns the name of a given function object
225 func FuncName(i interface{}) string {
226         return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
227 }
228
229 // SplitCommandArgs seperates multiple command arguments which may be quoted.
230 // The returned slice contains at least one string
231 func SplitCommandArgs(input string) []string {
232         var result []string
233         curArg := new(bytes.Buffer)
234         inQuote := false
235         escape := false
236
237         appendResult := func() {
238                 str := curArg.String()
239                 inQuote = false
240                 escape = false
241                 if strings.HasPrefix(str, `"`) && strings.HasSuffix(str, `"`) {
242                         if unquoted, err := strconv.Unquote(str); err == nil {
243                                 str = unquoted
244                         }
245                 }
246                 result = append(result, str)
247                 curArg.Reset()
248         }
249
250         for _, r := range input {
251                 if r == ' ' && !inQuote {
252                         appendResult()
253                 } else {
254                         curArg.WriteRune(r)
255
256                         if r == '"' && !inQuote {
257                                 inQuote = true
258                         } else {
259                                 if inQuote && !escape {
260                                         if r == '"' {
261                                                 inQuote = false
262                                         }
263                                         if r == '\\' {
264                                                 escape = true
265                                                 continue
266                                         }
267                                 }
268                         }
269                 }
270
271                 escape = false
272         }
273         appendResult()
274         return result
275 }
276
277 // JoinCommandArgs joins multiple command arguments and quote the strings if needed.
278 func JoinCommandArgs(args ...string) string {
279         buf := new(bytes.Buffer)
280         first := true
281         for _, arg := range args {
282                 if first {
283                         first = false
284                 } else {
285                         buf.WriteRune(' ')
286                 }
287                 quoted := strconv.Quote(arg)
288                 if quoted[1:len(quoted)-1] != arg || strings.ContainsRune(arg, ' ') {
289                         buf.WriteString(quoted)
290                 } else {
291                         buf.WriteString(arg)
292                 }
293         }
294
295         return buf.String()
296 }