18 "github.com/blang/semver"
19 runewidth "github.com/mattn/go-runewidth"
23 // These variables should be set by the linker when compiling
25 // Version is the version number or commit hash
26 Version = "0.0.0-unknown"
28 SemVersion semver.Version
29 // CommitHash is the commit this version was built on
30 CommitHash = "Unknown"
31 // CompileDate is the date this binary was compiled on
32 CompileDate = "Unknown"
35 // FakeCursor is used to disable the terminal cursor and have micro
36 // draw its own (enabled for windows consoles where the cursor is slow)
39 // Stdout is a buffer that is written to stdout when micro closes
45 SemVersion, err = semver.Make(Version)
47 fmt.Println("Invalid version: ", Version, err)
50 if runtime.GOOS == "windows" {
53 Stdout = new(bytes.Buffer)
56 // SliceEnd returns a byte slice where the index is a rune index
57 // Slices off the start of the slice
58 func SliceEnd(slc []byte, index int) []byte {
64 return slc[totalSize:]
67 _, size := utf8.DecodeRune(slc[totalSize:])
72 return slc[totalSize:]
75 // SliceEndStr is the same as SliceEnd but for strings
76 func SliceEndStr(str string, index int) string {
82 return str[totalSize:]
85 _, size := utf8.DecodeRuneInString(str[totalSize:])
90 return str[totalSize:]
93 // SliceStart returns a byte slice where the index is a rune index
94 // Slices off the end of the slice
95 func SliceStart(slc []byte, index int) []byte {
101 return slc[:totalSize]
104 _, size := utf8.DecodeRune(slc[totalSize:])
109 return slc[:totalSize]
112 // SliceStartStr is the same as SliceStart but for strings
113 func SliceStartStr(str string, index int) string {
117 for totalSize < len {
119 return str[:totalSize]
122 _, size := utf8.DecodeRuneInString(str[totalSize:])
127 return str[:totalSize]
130 // SliceVisualEnd will take a byte slice and slice off the start
131 // up to a given visual index. If the index is in the middle of a
132 // rune the number of visual columns into the rune will be returned
133 // It will also return the char pos of the first character of the slice
134 func SliceVisualEnd(b []byte, n, tabsize int) ([]byte, int, int) {
138 r, size := utf8.DecodeRune(b)
143 ts := tabsize - (width % tabsize)
146 w = runewidth.RuneWidth(r)
149 return b, n - width, i
155 return b, n - width, i
158 // Abs is a simple absolute value function for ints
159 func Abs(n int) int {
166 // StringWidth returns the visual width of a byte array indexed from 0 to n (rune index)
167 // with a given tabsize
168 func StringWidth(b []byte, n, tabsize int) int {
175 r, size := utf8.DecodeRune(b)
180 ts := tabsize - (width % tabsize)
183 width += runewidth.RuneWidth(r)
195 // Min takes the min of two ints
196 func Min(a, b int) int {
203 // Max takes the max of two ints
204 func Max(a, b int) int {
211 // FSize gets the size of a file
212 func FSize(f *os.File) int64 {
217 // IsWordChar returns whether or not the string is a 'word character'
218 // Word characters are defined as numbers, letters, or '_'
219 func IsWordChar(r rune) bool {
220 return unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_'
223 // Spaces returns a string with n spaces
224 func Spaces(n int) string {
225 return strings.Repeat(" ", n)
228 // IsSpaces checks if a given string is only spaces
229 func IsSpaces(str []byte) bool {
230 for _, c := range str {
239 // IsSpacesOrTabs checks if a given string contains only spaces and tabs
240 func IsSpacesOrTabs(str []byte) bool {
241 for _, c := range str {
242 if c != ' ' && c != '\t' {
250 // IsWhitespace returns true if the given rune is a space, tab, or newline
251 func IsWhitespace(c rune) bool {
252 return unicode.IsSpace(c)
255 // IsBytesWhitespace returns true if the given bytes are all whitespace
256 func IsBytesWhitespace(b []byte) bool {
257 for _, c := range b {
258 if !IsWhitespace(rune(c)) {
265 // RunePos returns the rune index of a given byte index
266 // Make sure the byte index is not between code points
267 func RunePos(b []byte, i int) int {
268 return utf8.RuneCount(b[:i])
271 // MakeRelative will attempt to make a relative path between path and base
272 func MakeRelative(path, base string) (string, error) {
274 rel, err := filepath.Rel(base, path)
283 // ReplaceHome takes a path as input and replaces ~ at the start of the path with the user's
284 // home directory. Does nothing if the path does not start with '~'.
285 func ReplaceHome(path string) (string, error) {
286 if !strings.HasPrefix(path, "~") {
290 var userData *user.User
293 homeString := strings.Split(path, "/")[0]
294 if homeString == "~" {
295 userData, err = user.Current()
297 return "", errors.New("Could not find user: " + err.Error())
300 userData, err = user.Lookup(homeString[1:])
302 return "", errors.New("Could not find user: " + err.Error())
306 home := userData.HomeDir
308 return strings.Replace(path, homeString, home, 1), nil
311 // GetPathAndCursorPosition returns a filename without everything following a `:`
312 // This is used for opening files like util.go:10:5 to specify a line and column
313 // Special cases like Windows Absolute path (C:\myfile.txt:10:5) are handled correctly.
314 func GetPathAndCursorPosition(path string) (string, []string) {
315 re := regexp.MustCompile(`([\s\S]+?)(?::(\d+))(?::(\d+))?`)
316 match := re.FindStringSubmatch(path)
317 // no lines/columns were specified in the path, return just the path with no cursor location
320 } else if match[len(match)-1] != "" {
321 // if the last capture group match isn't empty then both line and column were provided
322 return match[1], match[2:]
324 // if it was empty, then only a line was provided, so default to column 0
325 return match[1], []string{match[2], "0"}
328 // GetModTime returns the last modification time for a given file
329 func GetModTime(path string) (time.Time, error) {
330 info, err := os.Stat(path)
332 return time.Now(), err
334 return info.ModTime(), nil
337 // EscapePath replaces every path separator in a given path with a %
338 func EscapePath(path string) string {
339 path = filepath.ToSlash(path)
340 return strings.Replace(path, "/", "%", -1)
343 // GetLeadingWhitespace returns the leading whitespace of the given byte array
344 func GetLeadingWhitespace(b []byte) []byte {
347 r, size := utf8.DecodeRune(b)
348 if r == ' ' || r == '\t' {
349 ws = append(ws, byte(r))
359 // IntOpt turns a float64 setting to an int
360 func IntOpt(opt interface{}) int {
361 return int(opt.(float64))
364 // GetCharPosInLine gets the char position of a visual x y
365 // coordinate (this is necessary because tabs are 1 char but
367 func GetCharPosInLine(b []byte, visualPos int, tabsize int) int {
368 // Scan rune by rune until we exceed the visual width that we are
369 // looking for. Then we can return the character position we have found
371 width := 0 // string visual width
373 r, size := utf8.DecodeRune(b)
378 ts := tabsize - (width % tabsize)
381 width += runewidth.RuneWidth(r)
384 if width >= visualPos {
385 if width == visualPos {
396 // ParseBool is almost exactly like strconv.ParseBool, except it also accepts 'on' and 'off'
397 // as 'true' and 'false' respectively
398 func ParseBool(str string) (bool, error) {
405 return strconv.ParseBool(str)
408 // Clamp clamps a value between min and max
409 func Clamp(val, min, max int) int {
412 } else if val > max {
418 func IsNonAlphaNumeric(c rune) bool {
419 return !unicode.IsLetter(c) && !unicode.IsNumber(c)
422 func ParseSpecial(s string) string {
423 return strings.Replace(s, "\\t", "\t", -1)
426 // String converts a byte array to a string (for lua plugins)
427 func String(s []byte) string {