mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-29 22:27:13 +09:00
Reorganize file structure
This commit is contained in:
40
cmd/micro/util/message.go
Normal file
40
cmd/micro/util/message.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/zyedidia/micro/cmd/micro/screen"
|
||||
)
|
||||
|
||||
// TermMessage sends a message to the user in the terminal. This usually occurs before
|
||||
// micro has been fully initialized -- ie if there is an error in the syntax highlighting
|
||||
// regular expressions
|
||||
// The function must be called when the Screen is not initialized
|
||||
// This will write the message, and wait for the user
|
||||
// to press and key to continue
|
||||
func TermMessage(msg ...interface{}) {
|
||||
screenWasNil := screen.Screen == nil
|
||||
if !screenWasNil {
|
||||
screen.Screen.Fini()
|
||||
screen.Screen = nil
|
||||
}
|
||||
|
||||
fmt.Println(msg...)
|
||||
fmt.Print("\nPress enter to continue")
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
reader.ReadString('\n')
|
||||
|
||||
if !screenWasNil {
|
||||
screen.Init()
|
||||
}
|
||||
}
|
||||
|
||||
// TermError sends an error to the user in the terminal. Like TermMessage except formatted
|
||||
// as an error
|
||||
func TermError(filename string, lineNum int, err string) {
|
||||
TermMessage(filename + ", " + strconv.Itoa(lineNum) + ": " + err)
|
||||
}
|
||||
28
cmd/micro/util/profile.go
Normal file
28
cmd/micro/util/profile.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
)
|
||||
|
||||
func GetMemStats() string {
|
||||
var memstats runtime.MemStats
|
||||
runtime.ReadMemStats(&memstats)
|
||||
return fmt.Sprintf("Alloc: %s, Sys: %s, GC: %d, PauseTotalNs: %dns", humanize.Bytes(memstats.Alloc), humanize.Bytes(memstats.Sys), memstats.NumGC, memstats.PauseTotalNs)
|
||||
}
|
||||
|
||||
var start time.Time
|
||||
|
||||
func tic(s string) {
|
||||
log.Println("START:", s)
|
||||
start = time.Now()
|
||||
}
|
||||
|
||||
func toc() {
|
||||
end := time.Now()
|
||||
log.Println("END: ElapsedTime in seconds:", end.Sub(start))
|
||||
}
|
||||
222
cmd/micro/util/util.go
Normal file
222
cmd/micro/util/util.go
Normal file
@@ -0,0 +1,222 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// SliceEnd returns a byte slice where the index is a rune index
|
||||
// Slices off the start of the slice
|
||||
func SliceEnd(slc []byte, index int) []byte {
|
||||
len := len(slc)
|
||||
i := 0
|
||||
totalSize := 0
|
||||
for totalSize < len {
|
||||
if i >= index {
|
||||
return slc[totalSize:]
|
||||
}
|
||||
|
||||
_, size := utf8.DecodeRune(slc[totalSize:])
|
||||
totalSize += size
|
||||
i++
|
||||
}
|
||||
|
||||
return slc[totalSize:]
|
||||
}
|
||||
|
||||
// SliceStart returns a byte slice where the index is a rune index
|
||||
// Slices off the end of the slice
|
||||
func SliceStart(slc []byte, index int) []byte {
|
||||
len := len(slc)
|
||||
i := 0
|
||||
totalSize := 0
|
||||
for totalSize < len {
|
||||
if i >= index {
|
||||
return slc[:totalSize]
|
||||
}
|
||||
|
||||
_, size := utf8.DecodeRune(slc[totalSize:])
|
||||
totalSize += size
|
||||
i++
|
||||
}
|
||||
|
||||
return slc[:totalSize]
|
||||
}
|
||||
|
||||
// SliceVisualEnd will take a byte slice and slice off the start
|
||||
// up to a given visual index. If the index is in the middle of a
|
||||
// rune the number of visual columns into the rune will be returned
|
||||
func SliceVisualEnd(b []byte, n, tabsize int) ([]byte, int) {
|
||||
width := 0
|
||||
for len(b) > 0 {
|
||||
r, size := utf8.DecodeRune(b)
|
||||
|
||||
w := 0
|
||||
switch r {
|
||||
case '\t':
|
||||
ts := tabsize - (width % tabsize)
|
||||
w = ts
|
||||
default:
|
||||
w = runewidth.RuneWidth(r)
|
||||
}
|
||||
if width+w > n {
|
||||
return b, n - width
|
||||
}
|
||||
width += w
|
||||
b = b[size:]
|
||||
}
|
||||
return b, width
|
||||
}
|
||||
|
||||
// Abs is a simple absolute value function for ints
|
||||
func Abs(n int) int {
|
||||
if n < 0 {
|
||||
return -n
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// StringWidth returns the visual width of a byte array indexed from 0 to n (rune index)
|
||||
// with a given tabsize
|
||||
func StringWidth(b []byte, n, tabsize int) int {
|
||||
i := 0
|
||||
width := 0
|
||||
for len(b) > 0 {
|
||||
r, size := utf8.DecodeRune(b)
|
||||
b = b[size:]
|
||||
|
||||
switch r {
|
||||
case '\t':
|
||||
ts := tabsize - (width % tabsize)
|
||||
width += ts
|
||||
default:
|
||||
width += runewidth.RuneWidth(r)
|
||||
}
|
||||
|
||||
i++
|
||||
|
||||
if i == n {
|
||||
return width
|
||||
}
|
||||
}
|
||||
return width
|
||||
}
|
||||
|
||||
// Min takes the min of two ints
|
||||
func Min(a, b int) int {
|
||||
if a > b {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Max takes the max of two ints
|
||||
func Max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// FSize gets the size of a file
|
||||
func FSize(f *os.File) int64 {
|
||||
fi, _ := f.Stat()
|
||||
return fi.Size()
|
||||
}
|
||||
|
||||
// IsWordChar returns whether or not the string is a 'word character'
|
||||
// If it is a unicode character, then it does not match
|
||||
// Word characters are defined as [A-Za-z0-9_]
|
||||
func IsWordChar(str string) bool {
|
||||
if len(str) > 1 {
|
||||
// Unicode
|
||||
return true
|
||||
}
|
||||
c := str[0]
|
||||
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
|
||||
}
|
||||
|
||||
// IsWhitespace returns true if the given rune is a space, tab, or newline
|
||||
func IsWhitespace(c rune) bool {
|
||||
return c == ' ' || c == '\t' || c == '\n'
|
||||
}
|
||||
|
||||
// IsStrWhitespace returns true if the given string is all whitespace
|
||||
func IsStrWhitespace(str string) bool {
|
||||
// Range loop for unicode correctness
|
||||
for _, c := range str {
|
||||
if !IsWhitespace(c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// TODO: consider changing because of snap segfault
|
||||
// ReplaceHome takes a path as input and replaces ~ at the start of the path with the user's
|
||||
// home directory. Does nothing if the path does not start with '~'.
|
||||
func ReplaceHome(path string) (string, error) {
|
||||
if !strings.HasPrefix(path, "~") {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
var userData *user.User
|
||||
var err error
|
||||
|
||||
homeString := strings.Split(path, "/")[0]
|
||||
if homeString == "~" {
|
||||
userData, err = user.Current()
|
||||
if err != nil {
|
||||
return "", errors.New("Could not find user: " + err.Error())
|
||||
}
|
||||
} else {
|
||||
userData, err = user.Lookup(homeString[1:])
|
||||
if err != nil {
|
||||
return "", errors.New("Could not find user: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
home := userData.HomeDir
|
||||
|
||||
return strings.Replace(path, homeString, home, 1), nil
|
||||
}
|
||||
|
||||
// GetPathAndCursorPosition returns a filename without everything following a `:`
|
||||
// This is used for opening files like util.go:10:5 to specify a line and column
|
||||
// Special cases like Windows Absolute path (C:\myfile.txt:10:5) are handled correctly.
|
||||
func GetPathAndCursorPosition(path string) (string, []string) {
|
||||
re := regexp.MustCompile(`([\s\S]+?)(?::(\d+))(?::(\d+))?`)
|
||||
match := re.FindStringSubmatch(path)
|
||||
// no lines/columns were specified in the path, return just the path with no cursor location
|
||||
if len(match) == 0 {
|
||||
return path, nil
|
||||
} else if match[len(match)-1] != "" {
|
||||
// if the last capture group match isn't empty then both line and column were provided
|
||||
return match[1], match[2:]
|
||||
}
|
||||
// if it was empty, then only a line was provided, so default to column 0
|
||||
return match[1], []string{match[2], "0"}
|
||||
}
|
||||
|
||||
// GetModTime returns the last modification time for a given file
|
||||
func GetModTime(path string) (time.Time, error) {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return time.Now(), err
|
||||
}
|
||||
return info.ModTime(), nil
|
||||
}
|
||||
|
||||
// EscapePath replaces every path separator in a given path with a %
|
||||
func EscapePath(path string) string {
|
||||
path = filepath.ToSlash(path)
|
||||
return strings.Replace(path, "/", "%", -1)
|
||||
}
|
||||
33
cmd/micro/util/util_test.go
Normal file
33
cmd/micro/util/util_test.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStringWidth(t *testing.T) {
|
||||
bytes := []byte("\tPot să \tmănânc sticlă și ea nu mă rănește.")
|
||||
|
||||
n := StringWidth(bytes, 23, 4)
|
||||
assert.Equal(t, 26, n)
|
||||
}
|
||||
|
||||
func TestSliceVisualEnd(t *testing.T) {
|
||||
s := []byte("\thello")
|
||||
slc, n := SliceVisualEnd(s, 2, 4)
|
||||
assert.Equal(t, []byte("\thello"), slc)
|
||||
assert.Equal(t, 2, n)
|
||||
|
||||
slc, n = SliceVisualEnd(s, 1, 4)
|
||||
assert.Equal(t, []byte("\thello"), slc)
|
||||
assert.Equal(t, 1, n)
|
||||
|
||||
slc, n = SliceVisualEnd(s, 4, 4)
|
||||
assert.Equal(t, []byte("hello"), slc)
|
||||
assert.Equal(t, 0, n)
|
||||
|
||||
slc, n = SliceVisualEnd(s, 5, 4)
|
||||
assert.Equal(t, []byte("ello"), slc)
|
||||
assert.Equal(t, 0, n)
|
||||
}
|
||||
Reference in New Issue
Block a user