69dc54b407
Adding InitRuntimeFiles() to buffer_test.go has changed the behavior of this test: now it tests not just buffer editing per se, but also how well buffer editing works together with syntax highlighting (since InitRuntimeFiles() loads syntax files, and many of the test buffers match the json header pattern in the json.yaml syntax file, so they are "highlighted" as json). This revealed long existing races between buffer editing and syntax highlighting. Until we fix those races, temporarily disable InitRuntimeFiles() in this test.
341 lines
8.9 KiB
Go
341 lines
8.9 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
rt "github.com/zyedidia/micro/v2/runtime"
|
|
)
|
|
|
|
const (
|
|
RTColorscheme = 0
|
|
RTSyntax = 1
|
|
RTHelp = 2
|
|
RTPlugin = 3
|
|
RTSyntaxHeader = 4
|
|
)
|
|
|
|
var (
|
|
NumTypes = 5 // How many filetypes are there
|
|
)
|
|
|
|
type RTFiletype int
|
|
|
|
// 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 [][]RuntimeFile
|
|
var realFiles [][]RuntimeFile
|
|
|
|
func init() {
|
|
initRuntimeVars()
|
|
}
|
|
|
|
func initRuntimeVars() {
|
|
allFiles = make([][]RuntimeFile, NumTypes)
|
|
realFiles = make([][]RuntimeFile, NumTypes)
|
|
}
|
|
|
|
// NewRTFiletype creates a new RTFiletype
|
|
func NewRTFiletype() int {
|
|
NumTypes++
|
|
allFiles = append(allFiles, []RuntimeFile{})
|
|
realFiles = append(realFiles, []RuntimeFile{})
|
|
return NumTypes - 1
|
|
}
|
|
|
|
// 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 rt.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)
|
|
}
|
|
|
|
// AddRealRuntimeFile registers a file for the given filetype
|
|
func AddRealRuntimeFile(fileType RTFiletype, file RuntimeFile) {
|
|
allFiles[fileType] = append(allFiles[fileType], file)
|
|
realFiles[fileType] = append(realFiles[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())
|
|
AddRealRuntimeFile(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 := rt.AssetDir(directory)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
assetLoop:
|
|
for _, f := range files {
|
|
if ok, _ := path.Match(pattern, f); ok {
|
|
af := assetFile(path.Join(directory, f))
|
|
for _, rf := range realFiles[fileType] {
|
|
if af.Name() == rf.Name() {
|
|
continue assetLoop
|
|
}
|
|
}
|
|
AddRuntimeFile(fileType, af)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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]
|
|
}
|
|
|
|
// ListRealRuntimeFiles lists all real runtime files (on disk) for a filetype
|
|
// these runtime files will be ones defined by the user and loaded from the config directory
|
|
func ListRealRuntimeFiles(fileType RTFiletype) []RuntimeFile {
|
|
return realFiles[fileType]
|
|
}
|
|
|
|
// InitRuntimeFiles initializes all assets files and the config directory.
|
|
// If `user` is false, InitRuntimeFiles ignores the config directory and
|
|
// initializes asset files only.
|
|
func InitRuntimeFiles(user bool) {
|
|
add := func(fileType RTFiletype, dir, pattern string) {
|
|
if user {
|
|
AddRuntimeFilesFromDirectory(fileType, filepath.Join(ConfigDir, dir), pattern)
|
|
}
|
|
AddRuntimeFilesFromAssets(fileType, path.Join("runtime", dir), pattern)
|
|
}
|
|
|
|
initRuntimeVars()
|
|
|
|
add(RTColorscheme, "colorschemes", "*.micro")
|
|
add(RTSyntax, "syntax", "*.yaml")
|
|
add(RTSyntaxHeader, "syntax", "*.hdr")
|
|
add(RTHelp, "help", "*.md")
|
|
}
|
|
|
|
// InitPlugins initializes the plugins
|
|
func InitPlugins() {
|
|
Plugins = Plugins[:0]
|
|
initlua := filepath.Join(ConfigDir, "init.lua")
|
|
|
|
if _, err := os.Stat(initlua); !os.IsNotExist(err) {
|
|
p := new(Plugin)
|
|
p.Name = "initlua"
|
|
p.DirName = "initlua"
|
|
p.Srcs = append(p.Srcs, realFile(initlua))
|
|
Plugins = append(Plugins, p)
|
|
}
|
|
|
|
// Search ConfigDir for plugin-scripts
|
|
plugdir := filepath.Join(ConfigDir, "plug")
|
|
files, _ := ioutil.ReadDir(plugdir)
|
|
|
|
isID := regexp.MustCompile(`^[_A-Za-z0-9]+$`).MatchString
|
|
|
|
for _, d := range files {
|
|
plugpath := filepath.Join(plugdir, d.Name())
|
|
if stat, err := os.Stat(plugpath); err == nil && stat.IsDir() {
|
|
srcs, _ := ioutil.ReadDir(plugpath)
|
|
p := new(Plugin)
|
|
p.Name = d.Name()
|
|
p.DirName = d.Name()
|
|
for _, f := range srcs {
|
|
if strings.HasSuffix(f.Name(), ".lua") {
|
|
p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, d.Name(), f.Name())))
|
|
} else if strings.HasSuffix(f.Name(), ".json") {
|
|
data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), f.Name()))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
p.Info, err = NewPluginInfo(data)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
p.Name = p.Info.Name
|
|
}
|
|
}
|
|
|
|
if !isID(p.Name) || len(p.Srcs) <= 0 {
|
|
log.Println(p.Name, "is not a plugin")
|
|
continue
|
|
}
|
|
Plugins = append(Plugins, p)
|
|
}
|
|
}
|
|
|
|
plugdir = filepath.Join("runtime", "plugins")
|
|
if files, err := rt.AssetDir(plugdir); err == nil {
|
|
outer:
|
|
for _, d := range files {
|
|
for _, p := range Plugins {
|
|
if p.Name == d {
|
|
log.Println(p.Name, "built-in plugin overridden by user-defined one")
|
|
continue outer
|
|
}
|
|
}
|
|
|
|
if srcs, err := rt.AssetDir(filepath.Join(plugdir, d)); err == nil {
|
|
p := new(Plugin)
|
|
p.Name = d
|
|
p.DirName = d
|
|
p.Default = true
|
|
for _, f := range srcs {
|
|
if strings.HasSuffix(f, ".lua") {
|
|
p.Srcs = append(p.Srcs, assetFile(filepath.Join(plugdir, d, f)))
|
|
} else if strings.HasSuffix(f, ".json") {
|
|
data, err := rt.Asset(filepath.Join(plugdir, d, f))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
p.Info, err = NewPluginInfo(data)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
p.Name = p.Info.Name
|
|
}
|
|
}
|
|
if !isID(p.Name) || len(p.Srcs) <= 0 {
|
|
log.Println(p.Name, "is not a plugin")
|
|
continue
|
|
}
|
|
Plugins = append(Plugins, p)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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) error {
|
|
pl := FindPlugin(plugin)
|
|
if pl == nil {
|
|
return errors.New("Plugin " + plugin + " does not exist")
|
|
}
|
|
pldir := pl.DirName
|
|
fullpath := filepath.Join(ConfigDir, "plug", pldir, filePath)
|
|
if _, err := os.Stat(fullpath); err == nil {
|
|
AddRealRuntimeFile(filetype, realFile(fullpath))
|
|
} else {
|
|
fullpath = path.Join("runtime", "plugins", pldir, filePath)
|
|
AddRuntimeFile(filetype, assetFile(fullpath))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// PluginAddRuntimeFilesFromDirectory adds files from a directory to the runtime files for a plugin
|
|
func PluginAddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype, directory, pattern string) error {
|
|
pl := FindPlugin(plugin)
|
|
if pl == nil {
|
|
return errors.New("Plugin " + plugin + " does not exist")
|
|
}
|
|
pldir := pl.DirName
|
|
fullpath := filepath.Join(ConfigDir, "plug", pldir, directory)
|
|
if _, err := os.Stat(fullpath); err == nil {
|
|
AddRuntimeFilesFromDirectory(filetype, fullpath, pattern)
|
|
} else {
|
|
fullpath = path.Join("runtime", "plugins", pldir, directory)
|
|
AddRuntimeFilesFromAssets(filetype, fullpath, pattern)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// PluginAddRuntimeFileFromMemory adds a file to the runtime files for a plugin from a given string
|
|
func PluginAddRuntimeFileFromMemory(filetype RTFiletype, filename, data string) {
|
|
AddRealRuntimeFile(filetype, memoryFile{filename, []byte(data)})
|
|
}
|