mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-30 06:37:14 +09:00
Reorganize file structure
This commit is contained in:
256
cmd/micro/config/colorscheme.go
Normal file
256
cmd/micro/config/colorscheme.go
Normal file
@@ -0,0 +1,256 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
// Micro's default style
|
||||
var DefStyle tcell.Style = tcell.StyleDefault
|
||||
|
||||
// The current colorscheme
|
||||
var Colorscheme map[string]tcell.Style
|
||||
|
||||
// GetColor takes in a syntax group and returns the colorscheme's style for that group
|
||||
func GetColor(color string) tcell.Style {
|
||||
st := DefStyle
|
||||
if color == "" {
|
||||
return st
|
||||
}
|
||||
groups := strings.Split(color, ".")
|
||||
if len(groups) > 1 {
|
||||
curGroup := ""
|
||||
for i, g := range groups {
|
||||
if i != 0 {
|
||||
curGroup += "."
|
||||
}
|
||||
curGroup += g
|
||||
if style, ok := Colorscheme[curGroup]; ok {
|
||||
st = style
|
||||
}
|
||||
}
|
||||
} else if style, ok := Colorscheme[color]; ok {
|
||||
st = style
|
||||
} else {
|
||||
st = StringToStyle(color)
|
||||
}
|
||||
|
||||
return st
|
||||
}
|
||||
|
||||
// ColorschemeExists checks if a given colorscheme exists
|
||||
func ColorschemeExists(colorschemeName string) bool {
|
||||
return FindRuntimeFile(RTColorscheme, colorschemeName) != nil
|
||||
}
|
||||
|
||||
// InitColorscheme picks and initializes the colorscheme when micro starts
|
||||
func InitColorscheme() error {
|
||||
Colorscheme = make(map[string]tcell.Style)
|
||||
DefStyle = tcell.StyleDefault
|
||||
|
||||
return LoadDefaultColorscheme()
|
||||
}
|
||||
|
||||
// LoadDefaultColorscheme loads the default colorscheme from $(ConfigDir)/colorschemes
|
||||
func LoadDefaultColorscheme() error {
|
||||
return LoadColorscheme(GlobalSettings["colorscheme"].(string))
|
||||
}
|
||||
|
||||
// LoadColorscheme loads the given colorscheme from a directory
|
||||
func LoadColorscheme(colorschemeName string) error {
|
||||
file := FindRuntimeFile(RTColorscheme, colorschemeName)
|
||||
if file == nil {
|
||||
return errors.New(colorschemeName + " is not a valid colorscheme")
|
||||
}
|
||||
if data, err := file.Data(); err != nil {
|
||||
return errors.New("Error loading colorscheme: " + err.Error())
|
||||
} else {
|
||||
Colorscheme, err = ParseColorscheme(string(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseColorscheme parses the text definition for a colorscheme and returns the corresponding object
|
||||
// Colorschemes are made up of color-link statements linking a color group to a list of colors
|
||||
// For example, color-link keyword (blue,red) makes all keywords have a blue foreground and
|
||||
// red background
|
||||
func ParseColorscheme(text string) (map[string]tcell.Style, error) {
|
||||
var err error
|
||||
parser := regexp.MustCompile(`color-link\s+(\S*)\s+"(.*)"`)
|
||||
|
||||
lines := strings.Split(text, "\n")
|
||||
|
||||
c := make(map[string]tcell.Style)
|
||||
|
||||
for _, line := range lines {
|
||||
if strings.TrimSpace(line) == "" ||
|
||||
strings.TrimSpace(line)[0] == '#' {
|
||||
// Ignore this line
|
||||
continue
|
||||
}
|
||||
|
||||
matches := parser.FindSubmatch([]byte(line))
|
||||
if len(matches) == 3 {
|
||||
link := string(matches[1])
|
||||
colors := string(matches[2])
|
||||
|
||||
style := StringToStyle(colors)
|
||||
c[link] = style
|
||||
|
||||
if link == "default" {
|
||||
DefStyle = style
|
||||
}
|
||||
} else {
|
||||
err = errors.New("Color-link statement is not valid: " + line)
|
||||
}
|
||||
}
|
||||
|
||||
return c, err
|
||||
}
|
||||
|
||||
// StringToStyle returns a style from a string
|
||||
// The strings must be in the format "extra foregroundcolor,backgroundcolor"
|
||||
// The 'extra' can be bold, reverse, or underline
|
||||
func StringToStyle(str string) tcell.Style {
|
||||
var fg, bg string
|
||||
spaceSplit := strings.Split(str, " ")
|
||||
var split []string
|
||||
if len(spaceSplit) > 1 {
|
||||
split = strings.Split(spaceSplit[1], ",")
|
||||
} else {
|
||||
split = strings.Split(str, ",")
|
||||
}
|
||||
if len(split) > 1 {
|
||||
fg, bg = split[0], split[1]
|
||||
} else {
|
||||
fg = split[0]
|
||||
}
|
||||
fg = strings.TrimSpace(fg)
|
||||
bg = strings.TrimSpace(bg)
|
||||
|
||||
var fgColor, bgColor tcell.Color
|
||||
if fg == "" {
|
||||
fgColor, _, _ = DefStyle.Decompose()
|
||||
} else {
|
||||
fgColor = StringToColor(fg)
|
||||
}
|
||||
if bg == "" {
|
||||
_, bgColor, _ = DefStyle.Decompose()
|
||||
} else {
|
||||
bgColor = StringToColor(bg)
|
||||
}
|
||||
|
||||
style := DefStyle.Foreground(fgColor).Background(bgColor)
|
||||
if strings.Contains(str, "bold") {
|
||||
style = style.Bold(true)
|
||||
}
|
||||
if strings.Contains(str, "reverse") {
|
||||
style = style.Reverse(true)
|
||||
}
|
||||
if strings.Contains(str, "underline") {
|
||||
style = style.Underline(true)
|
||||
}
|
||||
return style
|
||||
}
|
||||
|
||||
// StringToColor returns a tcell color from a string representation of a color
|
||||
// We accept either bright... or light... to mean the brighter version of a color
|
||||
func StringToColor(str string) tcell.Color {
|
||||
switch str {
|
||||
case "black":
|
||||
return tcell.ColorBlack
|
||||
case "red":
|
||||
return tcell.ColorMaroon
|
||||
case "green":
|
||||
return tcell.ColorGreen
|
||||
case "yellow":
|
||||
return tcell.ColorOlive
|
||||
case "blue":
|
||||
return tcell.ColorNavy
|
||||
case "magenta":
|
||||
return tcell.ColorPurple
|
||||
case "cyan":
|
||||
return tcell.ColorTeal
|
||||
case "white":
|
||||
return tcell.ColorSilver
|
||||
case "brightblack", "lightblack":
|
||||
return tcell.ColorGray
|
||||
case "brightred", "lightred":
|
||||
return tcell.ColorRed
|
||||
case "brightgreen", "lightgreen":
|
||||
return tcell.ColorLime
|
||||
case "brightyellow", "lightyellow":
|
||||
return tcell.ColorYellow
|
||||
case "brightblue", "lightblue":
|
||||
return tcell.ColorBlue
|
||||
case "brightmagenta", "lightmagenta":
|
||||
return tcell.ColorFuchsia
|
||||
case "brightcyan", "lightcyan":
|
||||
return tcell.ColorAqua
|
||||
case "brightwhite", "lightwhite":
|
||||
return tcell.ColorWhite
|
||||
case "default":
|
||||
return tcell.ColorDefault
|
||||
default:
|
||||
// Check if this is a 256 color
|
||||
if num, err := strconv.Atoi(str); err == nil {
|
||||
return GetColor256(num)
|
||||
}
|
||||
// Probably a truecolor hex value
|
||||
return tcell.GetColor(str)
|
||||
}
|
||||
}
|
||||
|
||||
// GetColor256 returns the tcell color for a number between 0 and 255
|
||||
func GetColor256(color int) tcell.Color {
|
||||
colors := []tcell.Color{tcell.ColorBlack, tcell.ColorMaroon, tcell.ColorGreen,
|
||||
tcell.ColorOlive, tcell.ColorNavy, tcell.ColorPurple,
|
||||
tcell.ColorTeal, tcell.ColorSilver, tcell.ColorGray,
|
||||
tcell.ColorRed, tcell.ColorLime, tcell.ColorYellow,
|
||||
tcell.ColorBlue, tcell.ColorFuchsia, tcell.ColorAqua,
|
||||
tcell.ColorWhite, tcell.Color16, tcell.Color17, tcell.Color18, tcell.Color19, tcell.Color20,
|
||||
tcell.Color21, tcell.Color22, tcell.Color23, tcell.Color24, tcell.Color25, tcell.Color26, tcell.Color27, tcell.Color28,
|
||||
tcell.Color29, tcell.Color30, tcell.Color31, tcell.Color32, tcell.Color33, tcell.Color34, tcell.Color35, tcell.Color36,
|
||||
tcell.Color37, tcell.Color38, tcell.Color39, tcell.Color40, tcell.Color41, tcell.Color42, tcell.Color43, tcell.Color44,
|
||||
tcell.Color45, tcell.Color46, tcell.Color47, tcell.Color48, tcell.Color49, tcell.Color50, tcell.Color51, tcell.Color52,
|
||||
tcell.Color53, tcell.Color54, tcell.Color55, tcell.Color56, tcell.Color57, tcell.Color58, tcell.Color59, tcell.Color60,
|
||||
tcell.Color61, tcell.Color62, tcell.Color63, tcell.Color64, tcell.Color65, tcell.Color66, tcell.Color67, tcell.Color68,
|
||||
tcell.Color69, tcell.Color70, tcell.Color71, tcell.Color72, tcell.Color73, tcell.Color74, tcell.Color75, tcell.Color76,
|
||||
tcell.Color77, tcell.Color78, tcell.Color79, tcell.Color80, tcell.Color81, tcell.Color82, tcell.Color83, tcell.Color84,
|
||||
tcell.Color85, tcell.Color86, tcell.Color87, tcell.Color88, tcell.Color89, tcell.Color90, tcell.Color91, tcell.Color92,
|
||||
tcell.Color93, tcell.Color94, tcell.Color95, tcell.Color96, tcell.Color97, tcell.Color98, tcell.Color99, tcell.Color100,
|
||||
tcell.Color101, tcell.Color102, tcell.Color103, tcell.Color104, tcell.Color105, tcell.Color106, tcell.Color107, tcell.Color108,
|
||||
tcell.Color109, tcell.Color110, tcell.Color111, tcell.Color112, tcell.Color113, tcell.Color114, tcell.Color115, tcell.Color116,
|
||||
tcell.Color117, tcell.Color118, tcell.Color119, tcell.Color120, tcell.Color121, tcell.Color122, tcell.Color123, tcell.Color124,
|
||||
tcell.Color125, tcell.Color126, tcell.Color127, tcell.Color128, tcell.Color129, tcell.Color130, tcell.Color131, tcell.Color132,
|
||||
tcell.Color133, tcell.Color134, tcell.Color135, tcell.Color136, tcell.Color137, tcell.Color138, tcell.Color139, tcell.Color140,
|
||||
tcell.Color141, tcell.Color142, tcell.Color143, tcell.Color144, tcell.Color145, tcell.Color146, tcell.Color147, tcell.Color148,
|
||||
tcell.Color149, tcell.Color150, tcell.Color151, tcell.Color152, tcell.Color153, tcell.Color154, tcell.Color155, tcell.Color156,
|
||||
tcell.Color157, tcell.Color158, tcell.Color159, tcell.Color160, tcell.Color161, tcell.Color162, tcell.Color163, tcell.Color164,
|
||||
tcell.Color165, tcell.Color166, tcell.Color167, tcell.Color168, tcell.Color169, tcell.Color170, tcell.Color171, tcell.Color172,
|
||||
tcell.Color173, tcell.Color174, tcell.Color175, tcell.Color176, tcell.Color177, tcell.Color178, tcell.Color179, tcell.Color180,
|
||||
tcell.Color181, tcell.Color182, tcell.Color183, tcell.Color184, tcell.Color185, tcell.Color186, tcell.Color187, tcell.Color188,
|
||||
tcell.Color189, tcell.Color190, tcell.Color191, tcell.Color192, tcell.Color193, tcell.Color194, tcell.Color195, tcell.Color196,
|
||||
tcell.Color197, tcell.Color198, tcell.Color199, tcell.Color200, tcell.Color201, tcell.Color202, tcell.Color203, tcell.Color204,
|
||||
tcell.Color205, tcell.Color206, tcell.Color207, tcell.Color208, tcell.Color209, tcell.Color210, tcell.Color211, tcell.Color212,
|
||||
tcell.Color213, tcell.Color214, tcell.Color215, tcell.Color216, tcell.Color217, tcell.Color218, tcell.Color219, tcell.Color220,
|
||||
tcell.Color221, tcell.Color222, tcell.Color223, tcell.Color224, tcell.Color225, tcell.Color226, tcell.Color227, tcell.Color228,
|
||||
tcell.Color229, tcell.Color230, tcell.Color231, tcell.Color232, tcell.Color233, tcell.Color234, tcell.Color235, tcell.Color236,
|
||||
tcell.Color237, tcell.Color238, tcell.Color239, tcell.Color240, tcell.Color241, tcell.Color242, tcell.Color243, tcell.Color244,
|
||||
tcell.Color245, tcell.Color246, tcell.Color247, tcell.Color248, tcell.Color249, tcell.Color250, tcell.Color251, tcell.Color252,
|
||||
tcell.Color253, tcell.Color254, tcell.Color255,
|
||||
}
|
||||
|
||||
if color >= 0 && color < len(colors) {
|
||||
return colors[color]
|
||||
}
|
||||
|
||||
return tcell.ColorDefault
|
||||
}
|
||||
62
cmd/micro/config/colorscheme_test.go
Normal file
62
cmd/micro/config/colorscheme_test.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
func TestSimpleStringToStyle(t *testing.T) {
|
||||
s := StringToStyle("lightblue,magenta")
|
||||
|
||||
fg, bg, _ := s.Decompose()
|
||||
|
||||
assert.Equal(t, tcell.ColorBlue, fg)
|
||||
assert.Equal(t, tcell.ColorPurple, bg)
|
||||
}
|
||||
|
||||
func TestAttributeStringToStyle(t *testing.T) {
|
||||
s := StringToStyle("bold cyan,brightcyan")
|
||||
|
||||
fg, bg, attr := s.Decompose()
|
||||
|
||||
assert.Equal(t, tcell.ColorTeal, fg)
|
||||
assert.Equal(t, tcell.ColorAqua, bg)
|
||||
assert.NotEqual(t, 0, attr&tcell.AttrBold)
|
||||
}
|
||||
|
||||
func TestColor256StringToStyle(t *testing.T) {
|
||||
s := StringToStyle("128,60")
|
||||
|
||||
fg, bg, _ := s.Decompose()
|
||||
|
||||
assert.Equal(t, tcell.Color128, fg)
|
||||
assert.Equal(t, tcell.Color60, bg)
|
||||
}
|
||||
|
||||
func TestColorHexStringToStyle(t *testing.T) {
|
||||
s := StringToStyle("#deadbe,#ef1234")
|
||||
|
||||
fg, bg, _ := s.Decompose()
|
||||
|
||||
assert.Equal(t, tcell.NewRGBColor(222, 173, 190), fg)
|
||||
assert.Equal(t, tcell.NewRGBColor(239, 18, 52), bg)
|
||||
}
|
||||
|
||||
func TestColorschemeParser(t *testing.T) {
|
||||
testColorscheme := `color-link default "#F8F8F2,#282828"
|
||||
color-link comment "#75715E,#282828"
|
||||
# comment
|
||||
color-link identifier "#66D9EF,#282828" #comment
|
||||
color-link constant "#AE81FF,#282828"
|
||||
color-link constant.string "#E6DB74,#282828"
|
||||
color-link constant.string.char "#BDE6AD,#282828"`
|
||||
|
||||
c, err := ParseColorscheme(testColorscheme)
|
||||
assert.Nil(t, err)
|
||||
|
||||
fg, bg, _ := c["comment"].Decompose()
|
||||
assert.Equal(t, tcell.NewRGBColor(117, 113, 94), fg)
|
||||
assert.Equal(t, tcell.NewRGBColor(40, 40, 40), bg)
|
||||
}
|
||||
54
cmd/micro/config/config.go
Normal file
54
cmd/micro/config/config.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
var ConfigDir string
|
||||
|
||||
// InitConfigDir finds the configuration directory for micro according to the XDG spec.
|
||||
// If no directory is found, it creates one.
|
||||
func InitConfigDir(flagConfigDir string) error {
|
||||
var e error
|
||||
|
||||
xdgHome := os.Getenv("XDG_CONFIG_HOME")
|
||||
if xdgHome == "" {
|
||||
// The user has not set $XDG_CONFIG_HOME so we should act like it was set to ~/.config
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
return errors.New("Error finding your home directory\nCan't load config files")
|
||||
}
|
||||
xdgHome = home + "/.config"
|
||||
}
|
||||
ConfigDir = xdgHome + "/micro"
|
||||
|
||||
if len(flagConfigDir) > 0 {
|
||||
if _, err := os.Stat(flagConfigDir); os.IsNotExist(err) {
|
||||
e = errors.New("Error: " + flagConfigDir + " does not exist. Defaulting to " + ConfigDir + ".")
|
||||
} else {
|
||||
ConfigDir = flagConfigDir
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(xdgHome); os.IsNotExist(err) {
|
||||
// If the xdgHome doesn't exist we should create it
|
||||
err = os.Mkdir(xdgHome, os.ModePerm)
|
||||
if err != nil {
|
||||
return errors.New("Error creating XDG_CONFIG_HOME directory: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(ConfigDir); os.IsNotExist(err) {
|
||||
// If the micro specific config directory doesn't exist we should create that too
|
||||
err = os.Mkdir(ConfigDir, os.ModePerm)
|
||||
if err != nil {
|
||||
return errors.New("Error creating configuration directory: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
204
cmd/micro/config/rtfiles.go
Normal file
204
cmd/micro/config/rtfiles.go
Normal file
@@ -0,0 +1,204 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
RTColorscheme = 0
|
||||
RTSyntax = 1
|
||||
RTHelp = 2
|
||||
RTPlugin = 3
|
||||
NumTypes = 4 // How many filetypes are there
|
||||
)
|
||||
|
||||
type RTFiletype byte
|
||||
|
||||
// RuntimeFile allows the program to read runtime data like colorschemes or syntax files
|
||||
type RuntimeFile interface {
|
||||
// Name returns a name of the file without paths or extensions
|
||||
Name() string
|
||||
// Data returns the content of the file.
|
||||
Data() ([]byte, error)
|
||||
}
|
||||
|
||||
// allFiles contains all available files, mapped by filetype
|
||||
var allFiles [NumTypes][]RuntimeFile
|
||||
|
||||
// some file on filesystem
|
||||
type realFile string
|
||||
|
||||
// some asset file
|
||||
type assetFile string
|
||||
|
||||
// some file on filesystem but with a different name
|
||||
type namedFile struct {
|
||||
realFile
|
||||
name string
|
||||
}
|
||||
|
||||
// a file with the data stored in memory
|
||||
type memoryFile struct {
|
||||
name string
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (mf memoryFile) Name() string {
|
||||
return mf.name
|
||||
}
|
||||
func (mf memoryFile) Data() ([]byte, error) {
|
||||
return mf.data, nil
|
||||
}
|
||||
|
||||
func (rf realFile) Name() string {
|
||||
fn := filepath.Base(string(rf))
|
||||
return fn[:len(fn)-len(filepath.Ext(fn))]
|
||||
}
|
||||
|
||||
func (rf realFile) Data() ([]byte, error) {
|
||||
return ioutil.ReadFile(string(rf))
|
||||
}
|
||||
|
||||
func (af assetFile) Name() string {
|
||||
fn := path.Base(string(af))
|
||||
return fn[:len(fn)-len(path.Ext(fn))]
|
||||
}
|
||||
|
||||
func (af assetFile) Data() ([]byte, error) {
|
||||
return Asset(string(af))
|
||||
}
|
||||
|
||||
func (nf namedFile) Name() string {
|
||||
return nf.name
|
||||
}
|
||||
|
||||
// AddRuntimeFile registers a file for the given filetype
|
||||
func AddRuntimeFile(fileType RTFiletype, file RuntimeFile) {
|
||||
allFiles[fileType] = append(allFiles[fileType], file)
|
||||
}
|
||||
|
||||
// AddRuntimeFilesFromDirectory registers each file from the given directory for
|
||||
// the filetype which matches the file-pattern
|
||||
func AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string) {
|
||||
files, _ := ioutil.ReadDir(directory)
|
||||
for _, f := range files {
|
||||
if ok, _ := filepath.Match(pattern, f.Name()); !f.IsDir() && ok {
|
||||
fullPath := filepath.Join(directory, f.Name())
|
||||
AddRuntimeFile(fileType, realFile(fullPath))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddRuntimeFilesFromAssets registers each file from the given asset-directory for
|
||||
// the filetype which matches the file-pattern
|
||||
func AddRuntimeFilesFromAssets(fileType RTFiletype, directory, pattern string) {
|
||||
files, err := AssetDir(directory)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, f := range files {
|
||||
if ok, _ := path.Match(pattern, f); ok {
|
||||
AddRuntimeFile(fileType, assetFile(path.Join(directory, f)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FindRuntimeFile finds a runtime file of the given filetype and name
|
||||
// will return nil if no file was found
|
||||
func FindRuntimeFile(fileType RTFiletype, name string) RuntimeFile {
|
||||
for _, f := range ListRuntimeFiles(fileType) {
|
||||
if f.Name() == name {
|
||||
return f
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListRuntimeFiles lists all known runtime files for the given filetype
|
||||
func ListRuntimeFiles(fileType RTFiletype) []RuntimeFile {
|
||||
return allFiles[fileType]
|
||||
}
|
||||
|
||||
// InitRuntimeFiles initializes all assets file and the config directory
|
||||
func InitRuntimeFiles() {
|
||||
add := func(fileType RTFiletype, dir, pattern string) {
|
||||
AddRuntimeFilesFromDirectory(fileType, filepath.Join(ConfigDir, dir), pattern)
|
||||
AddRuntimeFilesFromAssets(fileType, path.Join("runtime", dir), pattern)
|
||||
}
|
||||
|
||||
add(RTColorscheme, "colorschemes", "*.micro")
|
||||
add(RTSyntax, "syntax", "*.yaml")
|
||||
add(RTHelp, "help", "*.md")
|
||||
|
||||
// Search ConfigDir for plugin-scripts
|
||||
files, _ := ioutil.ReadDir(filepath.Join(ConfigDir, "plugins"))
|
||||
for _, f := range files {
|
||||
realpath, _ := filepath.EvalSymlinks(filepath.Join(ConfigDir, "plugins", f.Name()))
|
||||
realpathStat, _ := os.Stat(realpath)
|
||||
if realpathStat.IsDir() {
|
||||
scriptPath := filepath.Join(ConfigDir, "plugins", f.Name(), f.Name()+".lua")
|
||||
if _, err := os.Stat(scriptPath); err == nil {
|
||||
AddRuntimeFile(RTPlugin, realFile(scriptPath))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if files, err := AssetDir("runtime/plugins"); err == nil {
|
||||
for _, f := range files {
|
||||
scriptPath := path.Join("runtime/plugins", f, f+".lua")
|
||||
if _, err := AssetInfo(scriptPath); err == nil {
|
||||
AddRuntimeFile(RTPlugin, assetFile(scriptPath))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PluginReadRuntimeFile allows plugin scripts to read the content of a runtime file
|
||||
func PluginReadRuntimeFile(fileType RTFiletype, name string) string {
|
||||
if file := FindRuntimeFile(fileType, name); file != nil {
|
||||
if data, err := file.Data(); err == nil {
|
||||
return string(data)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// PluginListRuntimeFiles allows plugins to lists all runtime files of the given type
|
||||
func PluginListRuntimeFiles(fileType RTFiletype) []string {
|
||||
files := ListRuntimeFiles(fileType)
|
||||
result := make([]string, len(files))
|
||||
for i, f := range files {
|
||||
result[i] = f.Name()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// PluginAddRuntimeFile adds a file to the runtime files for a plugin
|
||||
func PluginAddRuntimeFile(plugin string, filetype RTFiletype, filePath string) {
|
||||
fullpath := filepath.Join(ConfigDir, "plugins", plugin, filePath)
|
||||
if _, err := os.Stat(fullpath); err == nil {
|
||||
AddRuntimeFile(filetype, realFile(fullpath))
|
||||
} else {
|
||||
fullpath = path.Join("runtime", "plugins", plugin, filePath)
|
||||
AddRuntimeFile(filetype, assetFile(fullpath))
|
||||
}
|
||||
}
|
||||
|
||||
// PluginAddRuntimeFilesFromDirectory adds files from a directory to the runtime files for a plugin
|
||||
func PluginAddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype, directory, pattern string) {
|
||||
fullpath := filepath.Join(ConfigDir, "plugins", plugin, directory)
|
||||
if _, err := os.Stat(fullpath); err == nil {
|
||||
AddRuntimeFilesFromDirectory(filetype, fullpath, pattern)
|
||||
} else {
|
||||
fullpath = path.Join("runtime", "plugins", plugin, directory)
|
||||
AddRuntimeFilesFromAssets(filetype, fullpath, pattern)
|
||||
}
|
||||
}
|
||||
|
||||
// PluginAddRuntimeFileFromMemory adds a file to the runtime files for a plugin from a given string
|
||||
func PluginAddRuntimeFileFromMemory(plugin string, filetype RTFiletype, filename, data string) {
|
||||
AddRuntimeFile(filetype, memoryFile{filename, []byte(data)})
|
||||
}
|
||||
42
cmd/micro/config/rtfiles_test.go
Normal file
42
cmd/micro/config/rtfiles_test.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func init() {
|
||||
InitRuntimeFiles()
|
||||
}
|
||||
|
||||
func TestAddFile(t *testing.T) {
|
||||
AddRuntimeFile(RTPlugin, memoryFile{"foo.lua", []byte("hello world\n")})
|
||||
AddRuntimeFile(RTSyntax, memoryFile{"bar", []byte("some syntax file\n")})
|
||||
|
||||
f1 := FindRuntimeFile(RTPlugin, "foo.lua")
|
||||
assert.NotNil(t, f1)
|
||||
assert.Equal(t, "foo.lua", f1.Name())
|
||||
data, err := f1.Data()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []byte("hello world\n"), data)
|
||||
|
||||
f2 := FindRuntimeFile(RTSyntax, "bar")
|
||||
assert.NotNil(t, f2)
|
||||
assert.Equal(t, "bar", f2.Name())
|
||||
data, err = f2.Data()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []byte("some syntax file\n"), data)
|
||||
}
|
||||
|
||||
func TestFindFile(t *testing.T) {
|
||||
f := FindRuntimeFile(RTSyntax, "go")
|
||||
assert.NotNil(t, f)
|
||||
assert.Equal(t, "go", f.Name())
|
||||
data, err := f.Data()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []byte("filetype: go"), data[:12])
|
||||
|
||||
e := FindRuntimeFile(RTSyntax, "foobar")
|
||||
assert.Nil(t, e)
|
||||
}
|
||||
4027
cmd/micro/config/runtime.go
Normal file
4027
cmd/micro/config/runtime.go
Normal file
File diff suppressed because one or more lines are too long
455
cmd/micro/config/settings.go
Normal file
455
cmd/micro/config/settings.go
Normal file
@@ -0,0 +1,455 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/flynn/json5"
|
||||
"github.com/zyedidia/glob"
|
||||
)
|
||||
|
||||
type optionValidator func(string, interface{}) error
|
||||
|
||||
// The options that the user can set
|
||||
var GlobalSettings map[string]interface{}
|
||||
|
||||
// This is the raw parsed json
|
||||
var parsedSettings map[string]interface{}
|
||||
|
||||
// Options with validators
|
||||
var optionValidators = map[string]optionValidator{
|
||||
"tabsize": validatePositiveValue,
|
||||
"scrollmargin": validateNonNegativeValue,
|
||||
"scrollspeed": validateNonNegativeValue,
|
||||
"colorscheme": validateColorscheme,
|
||||
"colorcolumn": validateNonNegativeValue,
|
||||
"fileformat": validateLineEnding,
|
||||
}
|
||||
|
||||
func ReadSettings() error {
|
||||
filename := ConfigDir + "/settings.json"
|
||||
if _, e := os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return errors.New("Error reading settings.json file: " + err.Error())
|
||||
}
|
||||
if !strings.HasPrefix(string(input), "null") {
|
||||
// Unmarshal the input into the parsed map
|
||||
err = json5.Unmarshal(input, &parsedSettings)
|
||||
if err != nil {
|
||||
return errors.New("Error reading settings.json: " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitGlobalSettings initializes the options map and sets all options to their default values
|
||||
// Must be called after ReadSettings
|
||||
func InitGlobalSettings() {
|
||||
GlobalSettings = DefaultGlobalSettings()
|
||||
|
||||
for k, v := range parsedSettings {
|
||||
if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
|
||||
GlobalSettings[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InitLocalSettings scans the json in settings.json and sets the options locally based
|
||||
// on whether the filetype or path matches ft or glob local settings
|
||||
// Must be called after ReadSettings
|
||||
func InitLocalSettings(settings map[string]interface{}, path string) error {
|
||||
var parseError error
|
||||
for k, v := range parsedSettings {
|
||||
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
|
||||
if strings.HasPrefix(k, "ft:") {
|
||||
if settings["filetype"].(string) == k[3:] {
|
||||
for k1, v1 := range v.(map[string]interface{}) {
|
||||
settings[k1] = v1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
g, err := glob.Compile(k)
|
||||
if err != nil {
|
||||
parseError = errors.New("Error with glob setting " + k + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if g.MatchString(path) {
|
||||
for k1, v1 := range v.(map[string]interface{}) {
|
||||
settings[k1] = v1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return parseError
|
||||
}
|
||||
|
||||
// WriteSettings writes the settings to the specified filename as JSON
|
||||
func WriteSettings(filename string) error {
|
||||
var err error
|
||||
if _, e := os.Stat(ConfigDir); e == nil {
|
||||
for k, v := range GlobalSettings {
|
||||
parsedSettings[k] = v
|
||||
}
|
||||
|
||||
txt, _ := json.MarshalIndent(parsedSettings, "", " ")
|
||||
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// AddOption creates a new option. This is meant to be called by plugins to add options.
|
||||
func AddOption(name string, value interface{}) error {
|
||||
GlobalSettings[name] = value
|
||||
err := WriteSettings(ConfigDir + "/settings.json")
|
||||
if err != nil {
|
||||
return errors.New("Error writing settings.json file: " + err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetGlobalOption returns the global value of the given option
|
||||
func GetGlobalOption(name string) interface{} {
|
||||
return GlobalSettings[name]
|
||||
}
|
||||
|
||||
// GetLocalOption returns the local value of the given option
|
||||
// func GetLocalOption(name string, buf *Buffer) interface{} {
|
||||
// return buf.Settings[name]
|
||||
// }
|
||||
|
||||
// TODO: get option for current buffer
|
||||
// GetOption returns the value of the given option
|
||||
// If there is a local version of the option, it returns that
|
||||
// otherwise it will return the global version
|
||||
// func GetOption(name string) interface{} {
|
||||
// if GetLocalOption(name, CurView().Buf) != nil {
|
||||
// return GetLocalOption(name, CurView().Buf)
|
||||
// }
|
||||
// return GetGlobalOption(name)
|
||||
// }
|
||||
|
||||
func DefaultCommonSettings() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"autoindent": true,
|
||||
"autosave": false,
|
||||
"basename": false,
|
||||
"colorcolumn": float64(0),
|
||||
"cursorline": true,
|
||||
"eofnewline": false,
|
||||
"fastdirty": true,
|
||||
"fileformat": "unix",
|
||||
"hidehelp": false,
|
||||
"ignorecase": false,
|
||||
"indentchar": " ",
|
||||
"keepautoindent": false,
|
||||
"matchbrace": false,
|
||||
"matchbraceleft": false,
|
||||
"rmtrailingws": false,
|
||||
"ruler": true,
|
||||
"savecursor": false,
|
||||
"saveundo": false,
|
||||
"scrollbar": false,
|
||||
"scrollmargin": float64(3),
|
||||
"scrollspeed": float64(2),
|
||||
"softwrap": false,
|
||||
"smartpaste": true,
|
||||
"splitbottom": true,
|
||||
"splitright": true,
|
||||
"statusline": true,
|
||||
"syntax": true,
|
||||
"tabmovement": false,
|
||||
"tabsize": float64(4),
|
||||
"tabstospaces": false,
|
||||
"useprimary": true,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultGlobalSettings returns the default global settings for micro
|
||||
// Note that colorscheme is a global only option
|
||||
func DefaultGlobalSettings() map[string]interface{} {
|
||||
common := DefaultCommonSettings()
|
||||
common["colorscheme"] = "default"
|
||||
common["infobar"] = true
|
||||
common["keymenu"] = false
|
||||
common["mouse"] = true
|
||||
common["pluginchannels"] = []string{"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"}
|
||||
common["pluginrepos"] = []string{}
|
||||
common["savehistory"] = true
|
||||
common["sucmd"] = "sudo"
|
||||
common["termtitle"] = false
|
||||
return common
|
||||
}
|
||||
|
||||
// DefaultLocalSettings returns the default local settings
|
||||
// Note that filetype is a local only option
|
||||
func DefaultLocalSettings() map[string]interface{} {
|
||||
common := DefaultCommonSettings()
|
||||
common["filetype"] = "Unknown"
|
||||
return common
|
||||
}
|
||||
|
||||
// TODO: everything else
|
||||
|
||||
// SetOption attempts to set the given option to the value
|
||||
// By default it will set the option as global, but if the option
|
||||
// is local only it will set the local version
|
||||
// Use setlocal to force an option to be set locally
|
||||
// func SetOption(option, value string) error {
|
||||
// if _, ok := GlobalSettings[option]; !ok {
|
||||
// if _, ok := CurView().Buf.Settings[option]; !ok {
|
||||
// return errors.New("Invalid option")
|
||||
// }
|
||||
// SetLocalOption(option, value, CurView())
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// var nativeValue interface{}
|
||||
//
|
||||
// kind := reflect.TypeOf(GlobalSettings[option]).Kind()
|
||||
// if kind == reflect.Bool {
|
||||
// b, err := ParseBool(value)
|
||||
// if err != nil {
|
||||
// return errors.New("Invalid value")
|
||||
// }
|
||||
// nativeValue = b
|
||||
// } else if kind == reflect.String {
|
||||
// nativeValue = value
|
||||
// } else if kind == reflect.Float64 {
|
||||
// i, err := strconv.Atoi(value)
|
||||
// if err != nil {
|
||||
// return errors.New("Invalid value")
|
||||
// }
|
||||
// nativeValue = float64(i)
|
||||
// } else {
|
||||
// return errors.New("Option has unsupported value type")
|
||||
// }
|
||||
//
|
||||
// if err := optionIsValid(option, nativeValue); err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// GlobalSettings[option] = nativeValue
|
||||
//
|
||||
// if option == "colorscheme" {
|
||||
// // LoadSyntaxFiles()
|
||||
// InitColorscheme()
|
||||
// for _, tab := range tabs {
|
||||
// for _, view := range tab.Views {
|
||||
// view.Buf.UpdateRules()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if option == "infobar" || option == "keymenu" {
|
||||
// for _, tab := range tabs {
|
||||
// tab.Resize()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if option == "mouse" {
|
||||
// if !nativeValue.(bool) {
|
||||
// screen.DisableMouse()
|
||||
// } else {
|
||||
// screen.EnableMouse()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if len(tabs) != 0 {
|
||||
// if _, ok := CurView().Buf.Settings[option]; ok {
|
||||
// for _, tab := range tabs {
|
||||
// for _, view := range tab.Views {
|
||||
// SetLocalOption(option, value, view)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// // SetLocalOption sets the local version of this option
|
||||
// func SetLocalOption(option, value string, view *View) error {
|
||||
// buf := view.Buf
|
||||
// if _, ok := buf.Settings[option]; !ok {
|
||||
// return errors.New("Invalid option")
|
||||
// }
|
||||
//
|
||||
// var nativeValue interface{}
|
||||
//
|
||||
// kind := reflect.TypeOf(buf.Settings[option]).Kind()
|
||||
// if kind == reflect.Bool {
|
||||
// b, err := ParseBool(value)
|
||||
// if err != nil {
|
||||
// return errors.New("Invalid value")
|
||||
// }
|
||||
// nativeValue = b
|
||||
// } else if kind == reflect.String {
|
||||
// nativeValue = value
|
||||
// } else if kind == reflect.Float64 {
|
||||
// i, err := strconv.Atoi(value)
|
||||
// if err != nil {
|
||||
// return errors.New("Invalid value")
|
||||
// }
|
||||
// nativeValue = float64(i)
|
||||
// } else {
|
||||
// return errors.New("Option has unsupported value type")
|
||||
// }
|
||||
//
|
||||
// if err := optionIsValid(option, nativeValue); err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// if option == "fastdirty" {
|
||||
// // If it is being turned off, we have to hash every open buffer
|
||||
// var empty [md5.Size]byte
|
||||
// var wg sync.WaitGroup
|
||||
//
|
||||
// for _, tab := range tabs {
|
||||
// for _, v := range tab.Views {
|
||||
// if !nativeValue.(bool) {
|
||||
// if v.Buf.origHash == empty {
|
||||
// wg.Add(1)
|
||||
//
|
||||
// go func(b *Buffer) { // calculate md5 hash of the file
|
||||
// defer wg.Done()
|
||||
//
|
||||
// if file, e := os.Open(b.AbsPath); e == nil {
|
||||
// defer file.Close()
|
||||
//
|
||||
// h := md5.New()
|
||||
//
|
||||
// if _, e = io.Copy(h, file); e == nil {
|
||||
// h.Sum(b.origHash[:0])
|
||||
// }
|
||||
// }
|
||||
// }(v.Buf)
|
||||
// }
|
||||
// } else {
|
||||
// v.Buf.IsModified = v.Buf.Modified()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// wg.Wait()
|
||||
// }
|
||||
//
|
||||
// buf.Settings[option] = nativeValue
|
||||
//
|
||||
// if option == "statusline" {
|
||||
// view.ToggleStatusLine()
|
||||
// }
|
||||
//
|
||||
// if option == "filetype" {
|
||||
// // LoadSyntaxFiles()
|
||||
// InitColorscheme()
|
||||
// buf.UpdateRules()
|
||||
// }
|
||||
//
|
||||
// if option == "fileformat" {
|
||||
// buf.IsModified = true
|
||||
// }
|
||||
//
|
||||
// if option == "syntax" {
|
||||
// if !nativeValue.(bool) {
|
||||
// buf.ClearMatches()
|
||||
// } else {
|
||||
// if buf.highlighter != nil {
|
||||
// buf.highlighter.HighlightStates(buf)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// // SetOptionAndSettings sets the given option and saves the option setting to the settings config file
|
||||
// func SetOptionAndSettings(option, value string) {
|
||||
// filename := ConfigDir + "/settings.json"
|
||||
//
|
||||
// err := SetOption(option, value)
|
||||
//
|
||||
// if err != nil {
|
||||
// messenger.Error(err.Error())
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// err = WriteSettings(filename)
|
||||
// if err != nil {
|
||||
// messenger.Error("Error writing to settings.json: " + err.Error())
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
|
||||
func optionIsValid(option string, value interface{}) error {
|
||||
if validator, ok := optionValidators[option]; ok {
|
||||
return validator(option, value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Option validators
|
||||
|
||||
func validatePositiveValue(option string, value interface{}) error {
|
||||
tabsize, ok := value.(float64)
|
||||
|
||||
if !ok {
|
||||
return errors.New("Expected numeric type for " + option)
|
||||
}
|
||||
|
||||
if tabsize < 1 {
|
||||
return errors.New(option + " must be greater than 0")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateNonNegativeValue(option string, value interface{}) error {
|
||||
nativeValue, ok := value.(float64)
|
||||
|
||||
if !ok {
|
||||
return errors.New("Expected numeric type for " + option)
|
||||
}
|
||||
|
||||
if nativeValue < 0 {
|
||||
return errors.New(option + " must be non-negative")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateColorscheme(option string, value interface{}) error {
|
||||
colorscheme, ok := value.(string)
|
||||
|
||||
if !ok {
|
||||
return errors.New("Expected string type for colorscheme")
|
||||
}
|
||||
|
||||
if !ColorschemeExists(colorscheme) {
|
||||
return errors.New(colorscheme + " is not a valid colorscheme")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateLineEnding(option string, value interface{}) error {
|
||||
endingType, ok := value.(string)
|
||||
|
||||
if !ok {
|
||||
return errors.New("Expected string type for file format")
|
||||
}
|
||||
|
||||
if endingType != "unix" && endingType != "dos" {
|
||||
return errors.New("File format must be either 'unix' or 'dos'")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user