]> git.lizzy.rs Git - micro.git/blob - cmd/micro/util.go
Merge pull request #704 from zyedidia/multiple-cursors
[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 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 func FSize(f *os.File) int64 {
59         fi, _ := f.Stat()
60         // get the size
61         return fi.Size()
62 }
63
64 // IsWordChar returns whether or not the string is a 'word character'
65 // If it is a unicode character, then it does not match
66 // Word characters are defined as [A-Za-z0-9_]
67 func IsWordChar(str string) bool {
68         if len(str) > 1 {
69                 // Unicode
70                 return true
71         }
72         c := str[0]
73         return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
74 }
75
76 // IsWhitespace returns true if the given rune is a space, tab, or newline
77 func IsWhitespace(c rune) bool {
78         return c == ' ' || c == '\t' || c == '\n'
79 }
80
81 // IsStrWhitespace returns true if the given string is all whitespace
82 func IsStrWhitespace(str string) bool {
83         for _, c := range str {
84                 if !IsWhitespace(c) {
85                         return false
86                 }
87         }
88         return true
89 }
90
91 // Contains returns whether or not a string array contains a given string
92 func Contains(list []string, a string) bool {
93         for _, b := range list {
94                 if b == a {
95                         return true
96                 }
97         }
98         return false
99 }
100
101 // Insert makes a simple insert into a string at the given position
102 func Insert(str string, pos int, value string) string {
103         return string([]rune(str)[:pos]) + value + string([]rune(str)[pos:])
104 }
105
106 // MakeRelative will attempt to make a relative path between path and base
107 func MakeRelative(path, base string) (string, error) {
108         if len(path) > 0 {
109                 rel, err := filepath.Rel(base, path)
110                 if err != nil {
111                         return path, err
112                 }
113                 return rel, nil
114         }
115         return path, nil
116 }
117
118 // GetLeadingWhitespace returns the leading whitespace of the given string
119 func GetLeadingWhitespace(str string) string {
120         ws := ""
121         for _, c := range str {
122                 if c == ' ' || c == '\t' {
123                         ws += string(c)
124                 } else {
125                         break
126                 }
127         }
128         return ws
129 }
130
131 // IsSpaces checks if a given string is only spaces
132 func IsSpaces(str string) bool {
133         for _, c := range str {
134                 if c != ' ' {
135                         return false
136                 }
137         }
138
139         return true
140 }
141
142 // IsSpacesOrTabs checks if a given string contains only spaces and tabs
143 func IsSpacesOrTabs(str string) bool {
144         for _, c := range str {
145                 if c != ' ' && c != '\t' {
146                         return false
147                 }
148         }
149
150         return true
151 }
152
153 // ParseBool is almost exactly like strconv.ParseBool, except it also accepts 'on' and 'off'
154 // as 'true' and 'false' respectively
155 func ParseBool(str string) (bool, error) {
156         if str == "on" {
157                 return true, nil
158         }
159         if str == "off" {
160                 return false, nil
161         }
162         return strconv.ParseBool(str)
163 }
164
165 // EscapePath replaces every path separator in a given path with a %
166 func EscapePath(path string) string {
167         path = filepath.ToSlash(path)
168         return strings.Replace(path, "/", "%", -1)
169 }
170
171 // GetModTime returns the last modification time for a given file
172 // It also returns a boolean if there was a problem accessing the file
173 func GetModTime(path string) (time.Time, bool) {
174         info, err := os.Stat(path)
175         if err != nil {
176                 return time.Now(), false
177         }
178         return info.ModTime(), true
179 }
180
181 // StringWidth returns the width of a string where tabs count as `tabsize` width
182 func StringWidth(str string, tabsize int) int {
183         sw := runewidth.StringWidth(str)
184         lineIdx := 0
185         for _, ch := range str {
186                 switch ch {
187                 case '\t':
188                         ts := tabsize - (lineIdx % tabsize)
189                         sw += ts
190                         lineIdx += ts
191                 case '\n':
192                         lineIdx = 0
193                 default:
194                         lineIdx++
195                 }
196         }
197         return sw
198 }
199
200 // WidthOfLargeRunes searches all the runes in a string and counts up all the widths of runes
201 // that have a width larger than 1 (this also counts tabs as `tabsize` width)
202 func WidthOfLargeRunes(str string, tabsize int) int {
203         count := 0
204         lineIdx := 0
205         for _, ch := range str {
206                 var w int
207                 if ch == '\t' {
208                         w = tabsize - (lineIdx % tabsize)
209                 } else {
210                         w = runewidth.RuneWidth(ch)
211                 }
212                 if w > 1 {
213                         count += (w - 1)
214                 }
215                 if ch == '\n' {
216                         lineIdx = 0
217                 } else {
218                         lineIdx += w
219                 }
220         }
221         return count
222 }
223
224 // RunePos returns the rune index of a given byte index
225 // This could cause problems if the byte index is between code points
226 func runePos(p int, str string) int {
227         return utf8.RuneCountInString(str[:p])
228 }
229
230 func lcs(a, b string) string {
231         arunes := []rune(a)
232         brunes := []rune(b)
233
234         lcs := ""
235         for i, r := range arunes {
236                 if i >= len(brunes) {
237                         break
238                 }
239                 if r == brunes[i] {
240                         lcs += string(r)
241                 } else {
242                         break
243                 }
244         }
245         return lcs
246 }
247
248 func CommonSubstring(arr ...string) string {
249         commonStr := arr[0]
250
251         for _, str := range arr[1:] {
252                 commonStr = lcs(commonStr, str)
253         }
254
255         return commonStr
256 }
257
258 // Abs is a simple absolute value function for ints
259 func Abs(n int) int {
260         if n < 0 {
261                 return -n
262         }
263         return n
264 }
265
266 // FuncName returns the full name of a given function object
267 func FuncName(i interface{}) string {
268         return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
269 }
270
271 // ShortFuncName returns the name only of a given function object
272 func ShortFuncName(i interface{}) string {
273         return strings.TrimPrefix(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name(), "main.(*View).")
274 }
275
276 // SplitCommandArgs separates multiple command arguments which may be quoted.
277 // The returned slice contains at least one string
278 func SplitCommandArgs(input string) []string {
279         var result []string
280         var curQuote *bytes.Buffer
281
282         curArg := new(bytes.Buffer)
283         escape := false
284
285         finishQuote := func() {
286                 if curQuote == nil {
287                         return
288                 }
289                 str := curQuote.String()
290                 if unquoted, err := strconv.Unquote(str); err == nil {
291                         str = unquoted
292                 }
293                 curArg.WriteString(str)
294                 curQuote = nil
295         }
296
297         appendResult := func() {
298                 finishQuote()
299                 escape = false
300
301                 str := curArg.String()
302                 result = append(result, str)
303                 curArg.Reset()
304         }
305
306         for _, r := range input {
307                 if r == ' ' && curQuote == nil {
308                         appendResult()
309                 } else {
310                         runeHandled := false
311                         appendRuneToBuff := func() {
312                                 if curQuote != nil {
313                                         curQuote.WriteRune(r)
314                                 } else {
315                                         curArg.WriteRune(r)
316                                 }
317                                 runeHandled = true
318                         }
319
320                         if r == '"' && curQuote == nil {
321                                 curQuote = new(bytes.Buffer)
322                                 appendRuneToBuff()
323                         } else {
324                                 if curQuote != nil && !escape {
325                                         if r == '"' {
326                                                 appendRuneToBuff()
327                                                 finishQuote()
328                                         } else if r == '\\' {
329                                                 appendRuneToBuff()
330                                                 escape = true
331                                                 continue
332                                         }
333                                 }
334                         }
335                         if !runeHandled {
336                                 appendRuneToBuff()
337                         }
338                 }
339
340                 escape = false
341         }
342         appendResult()
343         return result
344 }
345
346 // JoinCommandArgs joins multiple command arguments and quote the strings if needed.
347 func JoinCommandArgs(args ...string) string {
348         buf := new(bytes.Buffer)
349         first := true
350         for _, arg := range args {
351                 if first {
352                         first = false
353                 } else {
354                         buf.WriteRune(' ')
355                 }
356                 quoted := strconv.Quote(arg)
357                 if quoted[1:len(quoted)-1] != arg || strings.ContainsRune(arg, ' ') {
358                         buf.WriteString(quoted)
359                 } else {
360                         buf.WriteString(arg)
361                 }
362         }
363
364         return buf.String()
365 }