17 "github.com/blang/semver"
18 runewidth "github.com/mattn/go-runewidth"
22 // These variables should be set by the linker when compiling
24 // Version is the version number or commit hash
25 Version = "0.0.0-unknown"
27 SemVersion semver.Version
28 // CommitHash is the commit this version was built on
29 CommitHash = "Unknown"
30 // CompileDate is the date this binary was compiled on
31 CompileDate = "Unknown"
34 // FakeCursor is used to disable the terminal cursor and have micro
35 // draw its own (enabled for windows consoles where the cursor is slow)
41 SemVersion, err = semver.Make(Version)
43 fmt.Println("Invalid version: ", Version, err)
46 if runtime.GOOS == "windows" {
51 // SliceEnd returns a byte slice where the index is a rune index
52 // Slices off the start of the slice
53 func SliceEnd(slc []byte, index int) []byte {
59 return slc[totalSize:]
62 _, size := utf8.DecodeRune(slc[totalSize:])
67 return slc[totalSize:]
70 // SliceEndStr is the same as SliceEnd but for strings
71 func SliceEndStr(str string, index int) string {
77 return str[totalSize:]
80 _, size := utf8.DecodeRuneInString(str[totalSize:])
85 return str[totalSize:]
88 // SliceStart returns a byte slice where the index is a rune index
89 // Slices off the end of the slice
90 func SliceStart(slc []byte, index int) []byte {
96 return slc[:totalSize]
99 _, size := utf8.DecodeRune(slc[totalSize:])
104 return slc[:totalSize]
107 // SliceStartStr is the same as SliceStart but for strings
108 func SliceStartStr(str string, index int) string {
112 for totalSize < len {
114 return str[:totalSize]
117 _, size := utf8.DecodeRuneInString(str[totalSize:])
122 return str[:totalSize]
125 // SliceVisualEnd will take a byte slice and slice off the start
126 // up to a given visual index. If the index is in the middle of a
127 // rune the number of visual columns into the rune will be returned
128 // It will also return the char pos of the first character of the slice
129 func SliceVisualEnd(b []byte, n, tabsize int) ([]byte, int, int) {
133 r, size := utf8.DecodeRune(b)
138 ts := tabsize - (width % tabsize)
141 w = runewidth.RuneWidth(r)
144 return b, n - width, i
150 return b, n - width, i
153 // Abs is a simple absolute value function for ints
154 func Abs(n int) int {
161 // StringWidth returns the visual width of a byte array indexed from 0 to n (rune index)
162 // with a given tabsize
163 func StringWidth(b []byte, n, tabsize int) int {
170 r, size := utf8.DecodeRune(b)
175 ts := tabsize - (width % tabsize)
178 width += runewidth.RuneWidth(r)
190 // Min takes the min of two ints
191 func Min(a, b int) int {
198 // Max takes the max of two ints
199 func Max(a, b int) int {
206 // FSize gets the size of a file
207 func FSize(f *os.File) int64 {
212 // IsWordChar returns whether or not the string is a 'word character'
213 // Word characters are defined as numbers, letters, or '_'
214 func IsWordChar(r rune) bool {
215 return unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_'
218 // Spaces returns a string with n spaces
219 func Spaces(n int) string {
220 return strings.Repeat(" ", n)
223 // IsSpaces checks if a given string is only spaces
224 func IsSpaces(str []byte) bool {
225 for _, c := range str {
234 // IsSpacesOrTabs checks if a given string contains only spaces and tabs
235 func IsSpacesOrTabs(str []byte) bool {
236 for _, c := range str {
237 if c != ' ' && c != '\t' {
245 // IsWhitespace returns true if the given rune is a space, tab, or newline
246 func IsWhitespace(c rune) bool {
247 return unicode.IsSpace(c)
250 // IsBytesWhitespace returns true if the given bytes are all whitespace
251 func IsBytesWhitespace(b []byte) bool {
252 for _, c := range b {
253 if !IsWhitespace(rune(c)) {
260 // RunePos returns the rune index of a given byte index
261 // Make sure the byte index is not between code points
262 func RunePos(b []byte, i int) int {
263 return utf8.RuneCount(b[:i])
266 // MakeRelative will attempt to make a relative path between path and base
267 func MakeRelative(path, base string) (string, error) {
269 rel, err := filepath.Rel(base, path)
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, error) {
281 if !strings.HasPrefix(path, "~") {
285 var userData *user.User
288 homeString := strings.Split(path, "/")[0]
289 if homeString == "~" {
290 userData, err = user.Current()
292 return "", errors.New("Could not find user: " + err.Error())
295 userData, err = user.Lookup(homeString[1:])
297 return "", errors.New("Could not find user: " + err.Error())
301 home := userData.HomeDir
303 return strings.Replace(path, homeString, home, 1), nil
306 // GetPathAndCursorPosition returns a filename without everything following a `:`
307 // This is used for opening files like util.go:10:5 to specify a line and column
308 // Special cases like Windows Absolute path (C:\myfile.txt:10:5) are handled correctly.
309 func GetPathAndCursorPosition(path string) (string, []string) {
310 re := regexp.MustCompile(`([\s\S]+?)(?::(\d+))(?::(\d+))?`)
311 match := re.FindStringSubmatch(path)
312 // no lines/columns were specified in the path, return just the path with no cursor location
315 } else if match[len(match)-1] != "" {
316 // if the last capture group match isn't empty then both line and column were provided
317 return match[1], match[2:]
319 // if it was empty, then only a line was provided, so default to column 0
320 return match[1], []string{match[2], "0"}
323 // GetModTime returns the last modification time for a given file
324 func GetModTime(path string) (time.Time, error) {
325 info, err := os.Stat(path)
327 return time.Now(), err
329 return info.ModTime(), nil
332 // EscapePath replaces every path separator in a given path with a %
333 func EscapePath(path string) string {
334 path = filepath.ToSlash(path)
335 return strings.Replace(path, "/", "%", -1)
338 // GetLeadingWhitespace returns the leading whitespace of the given byte array
339 func GetLeadingWhitespace(b []byte) []byte {
342 r, size := utf8.DecodeRune(b)
343 if r == ' ' || r == '\t' {
344 ws = append(ws, byte(r))
354 // IntOpt turns a float64 setting to an int
355 func IntOpt(opt interface{}) int {
356 return int(opt.(float64))
359 // GetCharPosInLine gets the char position of a visual x y
360 // coordinate (this is necessary because tabs are 1 char but
362 func GetCharPosInLine(b []byte, visualPos int, tabsize int) int {
363 // Scan rune by rune until we exceed the visual width that we are
364 // looking for. Then we can return the character position we have found
366 width := 0 // string visual width
368 r, size := utf8.DecodeRune(b)
373 ts := tabsize - (width % tabsize)
376 width += runewidth.RuneWidth(r)
379 if width >= visualPos {
380 if width == visualPos {
391 // ParseBool is almost exactly like strconv.ParseBool, except it also accepts 'on' and 'off'
392 // as 'true' and 'false' respectively
393 func ParseBool(str string) (bool, error) {
400 return strconv.ParseBool(str)
403 // Clamp clamps a value between min and max
404 func Clamp(val, min, max int) int {
407 } else if val > max {
413 func IsNonAlphaNumeric(c rune) bool {
414 return !unicode.IsLetter(c) && !unicode.IsNumber(c)
417 func ParseSpecial(s string) string {
418 return strings.Replace(s, "\\t", "\t", -1)
421 // String converts a byte array to a string (for lua plugins)
422 func String(s []byte) string {