mirror of
https://github.com/limetext/lime.git
synced 2024-11-23 19:47:23 +03:00
Switch the main project over to a meta project.
This commit is contained in:
parent
7eb4511afb
commit
1f1303a388
4
.gitattributes
vendored
4
.gitattributes
vendored
@ -1,4 +0,0 @@
|
||||
backend/textmate/testdata/* -text
|
||||
backend/testdata/* -text
|
||||
backend/loaders/json/testdata/* -text
|
||||
backend/loaders/plist/testdata/* -text
|
12
.gitignore
vendored
12
.gitignore
vendored
@ -1,12 +0,0 @@
|
||||
*.sublime-workspace
|
||||
*.sublime-project
|
||||
build/*
|
||||
!build/.gitkeep
|
||||
out.txt
|
||||
*.pyc
|
||||
*.log
|
||||
*.log.0*
|
||||
.DS_Store
|
||||
frontend/termbox/termbox
|
||||
frontend/html/html
|
||||
frontend/qml/qml
|
88
.gitmodules
vendored
88
.gitmodules
vendored
@ -1,88 +0,0 @@
|
||||
[submodule "packages/c.tmbundle"]
|
||||
path = packages/c.tmbundle
|
||||
url = https://github.com/textmate/c.tmbundle
|
||||
[submodule "packages/property-list.tmbundle"]
|
||||
path = packages/property-list.tmbundle
|
||||
url = https://github.com/textmate/property-list.tmbundle
|
||||
[submodule "packages/monokai.tmbundle"]
|
||||
path = packages/monokai.tmbundle
|
||||
url = https://github.com/textmate/monokai.tmbundle
|
||||
[submodule "packages/xml.tmbundle"]
|
||||
path = packages/xml.tmbundle
|
||||
url = https://github.com/textmate/xml.tmbundle.git
|
||||
[submodule "packages/go.tmbundle"]
|
||||
path = packages/go.tmbundle
|
||||
url = https://github.com/AlanQuatermain/go-tmbundle.git
|
||||
[submodule "packages/themes/TextMate-Themes"]
|
||||
path = packages/themes/TextMate-Themes
|
||||
url = https://github.com/filmgirl/TextMate-Themes.git
|
||||
[submodule "packages/GoSublime"]
|
||||
path = packages/GoSublime
|
||||
url = https://github.com/DisposaBoy/GoSublime.git
|
||||
[submodule "packages/Vintageous"]
|
||||
path = packages/Vintageous
|
||||
url = https://github.com/quarnster/Vintageous.git
|
||||
ignore = dirty
|
||||
[submodule "packages/themes/soda"]
|
||||
path = packages/themes/soda
|
||||
url = https://github.com/buymeasoda/soda-theme.git
|
||||
[submodule "packages/python.tmbundle"]
|
||||
path = packages/python.tmbundle
|
||||
url = https://github.com/textmate/python.tmbundle.git
|
||||
[submodule "packages/Diff.tmbundle"]
|
||||
path = packages/Diff.tmbundle
|
||||
url = https://github.com/textmate/Diff.tmbundle.git
|
||||
[submodule "packages/ruby.tmbundle"]
|
||||
path = packages/ruby.tmbundle
|
||||
url = https://github.com/textmate/ruby.tmbundle.git
|
||||
[submodule "packages/perl.tmbundle"]
|
||||
path = packages/perl.tmbundle
|
||||
url = https://github.com/textmate/perl.tmbundle.git
|
||||
[submodule "packages/sql.tmbundle"]
|
||||
path = packages/sql.tmbundle
|
||||
url = https://github.com/textmate/sql.tmbundle.git
|
||||
[submodule "packages/html.tmbundle"]
|
||||
path = packages/html.tmbundle
|
||||
url = https://github.com/textmate/html.tmbundle.git
|
||||
[submodule "packages/php.tmbundle"]
|
||||
path = packages/php.tmbundle
|
||||
url = https://github.com/textmate/php.tmbundle.git
|
||||
[submodule "packages/make.tmbundle"]
|
||||
path = packages/make.tmbundle
|
||||
url = https://github.com/textmate/make.tmbundle.git
|
||||
[submodule "packages/java.tmbundle"]
|
||||
path = packages/java.tmbundle
|
||||
url = https://github.com/textmate/java.tmbundle.git
|
||||
[submodule "packages/javascript.tmbundle"]
|
||||
path = packages/javascript.tmbundle
|
||||
url = https://github.com/textmate/javascript.tmbundle.git
|
||||
[submodule "packages/css.tmbundle"]
|
||||
path = packages/css.tmbundle
|
||||
url = https://github.com/textmate/css.tmbundle.git
|
||||
[submodule "packages/shellscript.tmbundle"]
|
||||
path = packages/shellscript.tmbundle
|
||||
url = https://github.com/textmate/shellscript.tmbundle.git
|
||||
[submodule "packages/json.tmbundle"]
|
||||
path = packages/json.tmbundle
|
||||
url = https://github.com/textmate/json.tmbundle.git
|
||||
[submodule "packages/markdown.tmbundle"]
|
||||
path = packages/markdown.tmbundle
|
||||
url = https://github.com/textmate/markdown.tmbundle.git
|
||||
[submodule "packages/haskell.tmbundle"]
|
||||
path = packages/haskell.tmbundle
|
||||
url = https://github.com/textmate/haskell.tmbundle.git
|
||||
[submodule "packages/lua.tmbundle"]
|
||||
path = packages/lua.tmbundle
|
||||
url = https://github.com/textmate/lua.tmbundle.git
|
||||
[submodule "packages/yaml.tmbundle"]
|
||||
path = packages/yaml.tmbundle
|
||||
url = https://github.com/textmate/yaml.tmbundle.git
|
||||
[submodule "packages/d.tmbundle"]
|
||||
path = packages/d.tmbundle
|
||||
url = https://github.com/textmate/d.tmbundle.git
|
||||
[submodule "packages/cmake.tmbundle"]
|
||||
path = packages/cmake.tmbundle
|
||||
url = https://github.com/textmate/cmake.tmbundle.git
|
||||
[submodule "packages/ini.tmbundle"]
|
||||
path = packages/ini.tmbundle
|
||||
url = https://github.com/textmate/ini.tmbundle.git
|
24
.travis.yml
24
.travis.yml
@ -1,24 +0,0 @@
|
||||
os:
|
||||
- osx
|
||||
- linux
|
||||
|
||||
language: go
|
||||
|
||||
go: 1.4
|
||||
|
||||
install:
|
||||
- ./tasks/ci/install.sh
|
||||
|
||||
script:
|
||||
- ./tasks/ci/run_tests.sh
|
||||
- ./tasks/ci/build_frontends.sh
|
||||
- ./tasks/ci/check_gen.sh
|
||||
- ./tasks/ci/check_fmt.sh
|
||||
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://webhooks.gitter.im/e/3e692a5224c8490f19bd
|
||||
on_success: change
|
||||
on_failure: always
|
||||
on_start: false
|
@ -12,14 +12,14 @@ All issues reported here should result in code being changed, added and/or remov
|
||||
|
||||
### 3. Feature requests belong here, unless they can be implemented via a plugin.
|
||||
|
||||
If the feature can be implemented via a plugin it fails by proxy to satisfy [#2](https://github.com/limetext/lime/blob/master/CONTRIBUTING.md#2-issues-should-be-openened-up-in-the-correct-repository).
|
||||
If the feature can be implemented via a plugin it fails by proxy to satisfy [#2](CONTRIBUTING.md#2-issues-should-be-openened-up-in-the-correct-repository).
|
||||
|
||||
|
||||
### 4. Bug reports belong here.
|
||||
|
||||
Bug reports should include a description of current behaviour and the expected behaviour in cases where this isn't obvious.
|
||||
|
||||
User errors where the error does not result in a code change count as support and fail to satisfy [#1](https://github.com/limetext/lime/blob/master/CONTRIBUTING.md#1-the-issues-list-is-for-development-purposes-only).
|
||||
User errors where the error does not result in a code change count as support and fail to satisfy [#1](CONTRIBUTING.md#1-the-issues-list-is-for-development-purposes-only).
|
||||
|
||||
### 5. If you'd like to help but don't know how.
|
||||
|
||||
|
2
LICENSE
2
LICENSE
@ -1,4 +1,4 @@
|
||||
Copyright (c) 2013 Fredrik Ehnbom and [contributors](https://github.com/limetext/lime/graphs/contributors)
|
||||
Copyright (c) 2013 The lime Authors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
18
README.md
18
README.md
@ -1,15 +1,21 @@
|
||||
[![Build Status](https://travis-ci.org/limetext/lime.svg?branch=master)](https://travis-ci.org/limetext/lime)
|
||||
[![Coverage Status](https://img.shields.io/coveralls/limetext/lime.svg?branch=master)](https://coveralls.io/r/limetext/lime?branch=master)
|
||||
[![GoDoc](https://godoc.org/github.com/limetext/lime?status.svg)](https://godoc.org/github.com/limetext/lime)
|
||||
[![Bountysource Bounties](https://www.bountysource.com/badge/team?team_id=8742&style=bounties_received)](https://www.bountysource.com/teams/limetext/issues?utm_source=limetext&utm_medium=shield&utm_campaign=bounties_received)
|
||||
[![Bountysource Raised](https://www.bountysource.com/badge/team?team_id=8742&style=raised)](https://www.bountysource.com/teams/limetext?utm_source=limetext&utm_medium=shield&utm_campaign=raised)
|
||||
|
||||
[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/limetext/lime)
|
||||
|
||||
|
||||
# lime
|
||||
|
||||
Welcome to the meta project for Lime.
|
||||
|
||||
For the backend, please see [limetext/lime-backend](https://github.com/limetext/lime-backend).
|
||||
|
||||
There are also three frontends currently in development: [limetext/lime-qml](https://github.com/limetext/lime-qml), [limetext/lime-termbox](https://github.com/limetext/lime-termbox), and [limetext/lime-html](https://github.com/limetext/lime-html).
|
||||
|
||||
|
||||
# Status
|
||||
|
||||
The [frontends](https://github.com/limetext/lime/issues?direction=desc&labels=frontend) are **not** ready to replace your favourite editor, but the backend itself isn't too far away.
|
||||
The frontends are **not** ready to replace your favourite editor, but the backend isn't too far away.
|
||||
|
||||
If you want to help us build Lime, great! We'd love your help. See [here](https://github.com/limetext/lime#contributing) for how you could be most useful.
|
||||
|
||||
@ -54,11 +60,11 @@ If your pull request is not accepted on the first try, don't be discouraged! If
|
||||
|
||||
Where is a good place to start? The [Contributing](https://github.com/limetext/lime/wiki/Contributing) page in the wiki has some suggestions. Think the [wiki](https://github.com/limetext/lime/wiki/_pages) needs clarifying, or something is missing? Go ahead and make the change.
|
||||
|
||||
Guidelines for making contributions can be found in [CONTRIBUTING.md](https://github.com/limetext/lime/blob/master/CONTRIBUTING.md).
|
||||
Guidelines for making contributions can be found in [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
Want to chat? Find us on [Gitter](https://gitter.im/limetext/lime).
|
||||
|
||||
|
||||
# License
|
||||
|
||||
The license of the project is the [2-clause BSD license](https://github.com/limetext/lime/blob/master/LICENSE).
|
||||
The license of the project is the [2-clause BSD license](LICENSE).
|
||||
|
@ -1,127 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package backend
|
||||
|
||||
type (
|
||||
// The Args type is just a generic string key and interface{} value
|
||||
// map type used to serialize command arguments.
|
||||
Args map[string]interface{}
|
||||
|
||||
// The CustomSet interface can be optionally implemented
|
||||
// by struct members of a concrete command struct.
|
||||
//
|
||||
// If implemented, it'll be called by the default
|
||||
// Command initialization code with the data gotten
|
||||
// from the Args map.
|
||||
CustomSet interface {
|
||||
Set(v interface{}) error
|
||||
}
|
||||
|
||||
CustomDefault interface {
|
||||
Default(key string) interface{}
|
||||
}
|
||||
|
||||
// The CustomInit interface can be optionally implemented
|
||||
// by a Command and will be called instead of the default
|
||||
// command initialization code.
|
||||
CustomInit interface {
|
||||
Init(args Args) error
|
||||
}
|
||||
|
||||
// The Command interface implements the basic interface
|
||||
// that is shared between the different more specific
|
||||
// command type interfaces.
|
||||
//
|
||||
// In the traditional Model-view-controller design,
|
||||
// Commands are roughly equivalent to the action-taking
|
||||
// controller piece.
|
||||
Command interface {
|
||||
// Returns whether the Command is enabled or not.
|
||||
IsEnabled() bool
|
||||
|
||||
// Returns whether the Command is visible in menus,
|
||||
// the goto anything panel or other user interface
|
||||
// that lists available commands.
|
||||
IsVisible() bool
|
||||
|
||||
// Returns the textual description of the command.
|
||||
Description() string
|
||||
|
||||
// Whether or not this Command bypasses the undo stack.
|
||||
BypassUndo() bool
|
||||
}
|
||||
|
||||
// The WindowCommand interface extends the base Command interface
|
||||
// with functionality specific for WindowCommands.
|
||||
WindowCommand interface {
|
||||
Command
|
||||
|
||||
// Execute this command with the specified window as the
|
||||
// argument
|
||||
Run(*Window) error
|
||||
}
|
||||
|
||||
// The TextCommand interface extends the base Command interface
|
||||
// with functionality specific for TextCommands.
|
||||
TextCommand interface {
|
||||
Command
|
||||
|
||||
// Execute this command with the specified View and Edit object
|
||||
// as the arguments
|
||||
Run(*View, *Edit) error
|
||||
}
|
||||
|
||||
// The ApplicationCommand interface extends the base Command interface
|
||||
// with functionality specific for ApplicationCommands.
|
||||
ApplicationCommand interface {
|
||||
Command
|
||||
|
||||
// Execute this command
|
||||
Run() error
|
||||
|
||||
// Returns whether this command is checked or not.
|
||||
// Used to display a checkbox in the user interface
|
||||
// for boolean commands.
|
||||
IsChecked() bool
|
||||
}
|
||||
|
||||
// The DefaultCommand implements the default operation
|
||||
// of the basic Command interface and is recommended to
|
||||
// be used as the base when creating new Commands.
|
||||
DefaultCommand struct{}
|
||||
|
||||
// The BypassUndoCommand is the same as the DefaultCommand
|
||||
// type, except that its implementation of BypassUndo returns
|
||||
// true rather than false.
|
||||
BypassUndoCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
)
|
||||
|
||||
// The default is to not bypass the undo stack.
|
||||
func (d *DefaultCommand) BypassUndo() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// By default a command is enabled.
|
||||
func (d *DefaultCommand) IsEnabled() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// By default a command is visible.
|
||||
func (d *DefaultCommand) IsVisible() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// By default the string "TODO" is return as the description.
|
||||
func (d *DefaultCommand) Description() string {
|
||||
return "TODO"
|
||||
}
|
||||
|
||||
// The BypassUndoCommand defaults to bypassing the
|
||||
// undo stack.
|
||||
func (b *BypassUndoCommand) BypassUndo() bool {
|
||||
return true
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package backend
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDefaultCommand(t *testing.T) {
|
||||
dc := DefaultCommand{}
|
||||
|
||||
if dc.BypassUndo() != false {
|
||||
t.Errorf("Expected BypassUndo to return false, but got %v", dc.BypassUndo())
|
||||
}
|
||||
|
||||
if dc.IsEnabled() != true {
|
||||
t.Errorf("Expected IsEnabled to return true, but got %v", dc.IsEnabled())
|
||||
}
|
||||
|
||||
if dc.IsVisible() != true {
|
||||
t.Errorf("Expected IsVisible to return true, but got %v", dc.IsVisible())
|
||||
}
|
||||
|
||||
if dc.Description() != "TODO" {
|
||||
t.Errorf("Expected Description to return \"TODO\", but got %v", dc.Description())
|
||||
}
|
||||
}
|
||||
|
||||
func TestBypassUndoCommand(t *testing.T) {
|
||||
bc := BypassUndoCommand{}
|
||||
|
||||
if bc.BypassUndo() != true {
|
||||
t.Errorf("Expected BypassUndo to return true, but got %v", bc.BypassUndo())
|
||||
}
|
||||
|
||||
if bc.IsEnabled() != true {
|
||||
t.Errorf("Expected IsEnabled to return true, but got %v", bc.IsEnabled())
|
||||
}
|
||||
|
||||
if bc.IsVisible() != true {
|
||||
t.Errorf("Expected IsVisible to return true, but got %v", bc.IsVisible())
|
||||
}
|
||||
|
||||
if bc.Description() != "TODO" {
|
||||
t.Errorf("Expected Description to return \"TODO\", but got %v", bc.Description())
|
||||
}
|
||||
}
|
@ -1,204 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/limetext/lime/backend/log"
|
||||
. "github.com/limetext/lime/backend/util"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
CommandHandler interface {
|
||||
Unregister(string) error
|
||||
RegisterWithDefault(cmd interface{}) error
|
||||
Register(name string, cmd interface{}) error
|
||||
// TODO(q): Do the commands need to be split in separate lists?
|
||||
RunWindowCommand(*Window, string, Args) error
|
||||
RunTextCommand(*View, string, Args) error
|
||||
RunApplicationCommand(string, Args) error
|
||||
}
|
||||
|
||||
appcmd map[string]Command
|
||||
textcmd map[string]Command
|
||||
wndcmd map[string]Command
|
||||
commandHandler struct {
|
||||
ApplicationCommands appcmd
|
||||
TextCommands textcmd
|
||||
WindowCommands wndcmd
|
||||
log bool
|
||||
verbose bool
|
||||
}
|
||||
)
|
||||
|
||||
func DefaultName(cmd interface{}) string {
|
||||
name := reflect.TypeOf(cmd).Elem().Name()
|
||||
return PascalCaseToSnakeCase(strings.TrimSuffix(name, "Command"))
|
||||
}
|
||||
|
||||
// If the cmd implements the CustomInit interface, its Init function
|
||||
// is called, otherwise the fields of the cmd's underlying struct type
|
||||
// will be enumerated and match against the dictionary keys in args,
|
||||
// or if the key isn't provided in args, the Zero value will be used.
|
||||
func (ch *commandHandler) init(cmd interface{}, args Args) error {
|
||||
if in, ok := cmd.(CustomInit); ok {
|
||||
return in.Init(args)
|
||||
}
|
||||
v := reflect.ValueOf(cmd).Elem()
|
||||
t := v.Type()
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
ft := t.Field(i)
|
||||
f := v.Field(i)
|
||||
if ft.Anonymous || !f.CanSet() {
|
||||
continue
|
||||
}
|
||||
key := PascalCaseToSnakeCase(ft.Name)
|
||||
fv, ok := args[key]
|
||||
if !ok {
|
||||
fv = reflect.Zero(ft.Type).Interface()
|
||||
if def, ok := cmd.(CustomDefault); ok {
|
||||
if val := def.Default(key); val != nil {
|
||||
fv = val
|
||||
}
|
||||
}
|
||||
}
|
||||
if f.CanAddr() {
|
||||
if f2, ok := f.Addr().Interface().(CustomSet); ok {
|
||||
if err := f2.Set(fv); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
f.Set(reflect.ValueOf(fv))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ch *commandHandler) RunWindowCommand(wnd *Window, name string, args Args) error {
|
||||
lvl := log.FINE
|
||||
p := Prof.Enter("wc")
|
||||
defer p.Exit()
|
||||
if ch.log {
|
||||
lvl = log.DEBUG
|
||||
}
|
||||
log.Logf(lvl, "Running window command: %s %v", name, args)
|
||||
t := time.Now()
|
||||
if c, ok := ch.WindowCommands[name].(WindowCommand); c != nil && ok {
|
||||
if err := ch.init(c, args); err != nil && ch.verbose {
|
||||
log.Debug("Command initialization failed: %s", err)
|
||||
return err
|
||||
} else if err := wnd.runCommand(c, name); err != nil {
|
||||
log.Logf(lvl+1, "Command execution failed: %s", err)
|
||||
return err
|
||||
} else {
|
||||
log.Logf(lvl, "Ran Window command: %s %s", name, time.Since(t))
|
||||
}
|
||||
} else {
|
||||
log.Logf(lvl, "No such window command: %s", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ch *commandHandler) RunTextCommand(view *View, name string, args Args) error {
|
||||
lvl := log.FINE
|
||||
p := Prof.Enter("tc")
|
||||
defer p.Exit()
|
||||
t := time.Now()
|
||||
if ch.log {
|
||||
lvl = log.DEBUG
|
||||
}
|
||||
log.Logf(lvl, "Running text command: %s %v", name, args)
|
||||
if c, ok := ch.TextCommands[name].(TextCommand); c != nil && ok {
|
||||
if err := ch.init(c, args); err != nil && ch.verbose {
|
||||
log.Debug("Command initialization failed: %s", err)
|
||||
return err
|
||||
} else if err := view.runCommand(c, name); err != nil {
|
||||
log.Logf(lvl, "Command execution failed: %s", err)
|
||||
return err
|
||||
}
|
||||
} else if w := view.Window(); w != nil {
|
||||
if c, ok := ch.WindowCommands[name].(WindowCommand); c != nil && ok {
|
||||
if err := w.runCommand(c, name); err != nil {
|
||||
log.Logf(lvl, "Command execution failed: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Logf(lvl, "Ran text command: %s %s", name, time.Since(t))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ch *commandHandler) RunApplicationCommand(name string, args Args) error {
|
||||
p := Prof.Enter("ac")
|
||||
defer p.Exit()
|
||||
if ch.log {
|
||||
log.Info("Running application command: %s %v", name, args)
|
||||
} else {
|
||||
log.Fine("Running application command: %s %v", name, args)
|
||||
}
|
||||
if c, ok := ch.ApplicationCommands[name].(ApplicationCommand); c != nil && ok {
|
||||
if err := ch.init(c, args); err != nil && ch.verbose {
|
||||
log.Debug("Command initialization failed: %s", err)
|
||||
return err
|
||||
} else if err := c.Run(); err != nil && ch.verbose {
|
||||
log.Debug("Command execution failed: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ch *commandHandler) Unregister(name string) error {
|
||||
if _, ok := ch.ApplicationCommands[name]; ok {
|
||||
ch.ApplicationCommands[name] = nil
|
||||
} else if _, ok := ch.WindowCommands[name]; ok {
|
||||
ch.WindowCommands[name] = nil
|
||||
} else if _, ok := ch.TextCommands[name]; ok {
|
||||
ch.TextCommands[name] = nil
|
||||
} else {
|
||||
return fmt.Errorf("%s wasn't a registered command", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ch *commandHandler) RegisterWithDefault(cmd interface{}) error {
|
||||
return ch.Register(DefaultName(cmd), cmd)
|
||||
}
|
||||
|
||||
func (ch *commandHandler) Register(name string, cmd interface{}) error {
|
||||
var r = false
|
||||
log.Finest("Want to register %s", name)
|
||||
if ac, ok := cmd.(ApplicationCommand); ok {
|
||||
if _, ok := ch.ApplicationCommands[name]; ok {
|
||||
return fmt.Errorf("%s is already a registered command", name)
|
||||
}
|
||||
r = true
|
||||
ch.ApplicationCommands[name] = ac
|
||||
}
|
||||
if wc, ok := cmd.(WindowCommand); ok {
|
||||
if _, ok := ch.WindowCommands[name]; ok {
|
||||
return fmt.Errorf("%s is already a registered command", name)
|
||||
}
|
||||
r = true
|
||||
ch.WindowCommands[name] = wc
|
||||
}
|
||||
if tc, ok := cmd.(TextCommand); ok {
|
||||
if _, ok := ch.TextCommands[name]; ok {
|
||||
return fmt.Errorf("%s is already a registered command", name)
|
||||
}
|
||||
r = true
|
||||
ch.TextCommands[name] = tc
|
||||
}
|
||||
if !r {
|
||||
return fmt.Errorf("Command wasn't registered in any list: %s", name)
|
||||
} else if ch.verbose {
|
||||
log.Finest("Successfully registered command %s", name)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,220 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type (
|
||||
DummyApplicationCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
|
||||
DummyWindowCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
|
||||
DummyTextCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
)
|
||||
|
||||
func (c *DummyApplicationCommand) Run() error {
|
||||
return fmt.Errorf("Ran")
|
||||
}
|
||||
|
||||
func (c *DummyApplicationCommand) IsChecked() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *DummyWindowCommand) Run(w *Window) error {
|
||||
return fmt.Errorf("Ran")
|
||||
}
|
||||
|
||||
func (c *DummyTextCommand) Run(v *View, e *Edit) error {
|
||||
return fmt.Errorf("Ran")
|
||||
}
|
||||
|
||||
func TestDefaultName(t *testing.T) {
|
||||
n := DefaultName(&DummyApplicationCommand{})
|
||||
|
||||
if n != "dummy_application" {
|
||||
t.Errorf("Expected %s, but got %s", "dummy_application", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterAndRunApplicationCommand(t *testing.T) {
|
||||
name := "app_test"
|
||||
ac := DummyApplicationCommand{}
|
||||
ch := GetEditor().CommandHandler()
|
||||
|
||||
err := ch.Register(name, &ac)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Got error while registering: %s", err)
|
||||
}
|
||||
|
||||
err = ch.RunApplicationCommand(name, Args{})
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected %s to run, but it didn't", name)
|
||||
} else if err.Error() != "Ran" {
|
||||
t.Errorf("Expected %s to run, but it got an error: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterAndRunWindowCommand(t *testing.T) {
|
||||
ed := GetEditor()
|
||||
|
||||
name := "wnd_test"
|
||||
wc := DummyWindowCommand{}
|
||||
ch := ed.CommandHandler()
|
||||
|
||||
err := ch.Register(name, &wc)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Got error while registering: %s", err)
|
||||
}
|
||||
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
err = ch.RunWindowCommand(w, name, Args{})
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected %s to run, but it didn't", name)
|
||||
} else if err.Error() != "Ran" {
|
||||
t.Errorf("Expected %s to run, but it got an error: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterAndRunTextCommand(t *testing.T) {
|
||||
ed := GetEditor()
|
||||
|
||||
name := "text_test"
|
||||
tc := DummyTextCommand{}
|
||||
ch := ed.CommandHandler()
|
||||
|
||||
err := ch.Register(name, &tc)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Got error while registering: %s", err)
|
||||
}
|
||||
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
v := w.NewFile()
|
||||
defer v.Close()
|
||||
|
||||
err = ch.RunTextCommand(v, name, Args{})
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected %s to run, but it didn't", name)
|
||||
} else if err.Error() != "Ran" {
|
||||
t.Errorf("Expected %s to run, but it got an error: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterAndRunDefaultNamedCommand(t *testing.T) {
|
||||
ac := DummyApplicationCommand{}
|
||||
ch := GetEditor().CommandHandler()
|
||||
|
||||
err := ch.RegisterWithDefault(&ac)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Got error while registering: %s", err)
|
||||
}
|
||||
|
||||
name := DefaultName(&ac)
|
||||
err = ch.RunApplicationCommand(name, Args{})
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected %s to run, but it didn't", name)
|
||||
} else if err.Error() != "Ran" {
|
||||
t.Errorf("Expected %s to run, but it got an error: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnregisterAndRunApplicationCommand(t *testing.T) {
|
||||
name := "app_test_unregister"
|
||||
ac := DummyApplicationCommand{}
|
||||
ch := GetEditor().CommandHandler()
|
||||
|
||||
_ = ch.Register(name, &ac)
|
||||
err := ch.Unregister(name)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Got error while unregistering: %s", err)
|
||||
}
|
||||
|
||||
err = ch.RunApplicationCommand(name, Args{})
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Expected %s not to run, but it did", name)
|
||||
if err.Error() != "Ran" {
|
||||
t.Errorf("Expected %s not to run, but it got an error: %v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnregisterAndRunWindowCommand(t *testing.T) {
|
||||
ed := GetEditor()
|
||||
|
||||
name := "wnd_test_unregister"
|
||||
wc := DummyWindowCommand{}
|
||||
ch := ed.CommandHandler()
|
||||
|
||||
_ = ch.Register(name, &wc)
|
||||
err := ch.Unregister(name)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Got error while unregistering: %s", err)
|
||||
}
|
||||
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
err = ch.RunWindowCommand(w, name, Args{})
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Expected %s not to run, but it did", name)
|
||||
if err.Error() != "Ran" {
|
||||
t.Errorf("Expected %s not to run, but it got an error: %v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnregisterAndRunTextCommand(t *testing.T) {
|
||||
ed := GetEditor()
|
||||
|
||||
name := "text_test"
|
||||
tc := DummyTextCommand{}
|
||||
ch := ed.CommandHandler()
|
||||
|
||||
_ = ch.Register(name, &tc)
|
||||
err := ch.Unregister(name)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Got error while unregistering: %s", err)
|
||||
}
|
||||
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
v := w.NewFile()
|
||||
defer v.Close()
|
||||
|
||||
err = ch.RunTextCommand(v, name, Args{})
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Expected %s not to run, but it did", name)
|
||||
if err.Error() != "Ran" {
|
||||
t.Errorf("Expected %s not to run, but it got an error: %v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type (
|
||||
// The TitleCaseCommand transforms all selections
|
||||
// to be in Title Case. For instance, the text:
|
||||
// "this is some sample text"
|
||||
// turns in to:
|
||||
// "This Is Some Sample Text"
|
||||
TitleCaseCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
|
||||
// The SwapCaseCommand transforms all selections
|
||||
// so that each character in the selection
|
||||
// is the opposite case. For example, the text:
|
||||
// "Hello, World!"
|
||||
// turns in to:
|
||||
// "hELLO, wORLD!"
|
||||
SwapCaseCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
|
||||
// The UpperCaseCommand transforms all selections
|
||||
// so that each character in the selection
|
||||
// is in its upper case equivalent (if any.)
|
||||
UpperCaseCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
|
||||
// The LowerCaseCommand transforms all selections
|
||||
// so that each character in the selection
|
||||
// is in its lower case equivalent
|
||||
LowerCaseCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
)
|
||||
|
||||
func (c *TitleCaseCommand) Run(v *View, e *Edit) error {
|
||||
sel := v.Sel()
|
||||
for i := 0; i < sel.Len(); i++ {
|
||||
r := sel.Get(i)
|
||||
if r.Size() != 0 {
|
||||
t := v.Buffer().Substr(r)
|
||||
v.Replace(e, r, strings.Title(t))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SwapCaseCommand) Run(v *View, e *Edit) error {
|
||||
sel := v.Sel()
|
||||
for i := 0; i < sel.Len(); i++ {
|
||||
r := sel.Get(i)
|
||||
if r.Size() == 0 {
|
||||
continue
|
||||
}
|
||||
text := v.Buffer().Substr(r)
|
||||
swapped := make([]rune, 0)
|
||||
for _, c := range text {
|
||||
if unicode.IsUpper(c) {
|
||||
swapped = append(swapped, unicode.ToLower(c))
|
||||
} else {
|
||||
swapped = append(swapped, unicode.ToUpper(c))
|
||||
}
|
||||
}
|
||||
v.Replace(e, r, string(swapped))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *UpperCaseCommand) Run(v *View, e *Edit) error {
|
||||
sel := v.Sel()
|
||||
for i := 0; i < sel.Len(); i++ {
|
||||
r := sel.Get(i)
|
||||
if r.Size() != 0 {
|
||||
t := v.Buffer().Substr(r)
|
||||
v.Replace(e, r, strings.ToUpper(t))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *LowerCaseCommand) Run(v *View, e *Edit) error {
|
||||
sel := v.Sel()
|
||||
for i := 0; i < sel.Len(); i++ {
|
||||
r := sel.Get(i)
|
||||
if r.Size() != 0 {
|
||||
t := v.Buffer().Substr(r)
|
||||
v.Replace(e, r, strings.ToLower(t))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
register([]Command{
|
||||
&TitleCaseCommand{},
|
||||
&SwapCaseCommand{},
|
||||
&UpperCaseCommand{},
|
||||
&LowerCaseCommand{},
|
||||
})
|
||||
}
|
@ -1,201 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
. "github.com/limetext/text"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type caseTest struct {
|
||||
in_region []Region
|
||||
in string
|
||||
exp string
|
||||
}
|
||||
|
||||
func runCaseTest(command string, testsuite *[]caseTest, t *testing.T) {
|
||||
ed := GetEditor()
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
for i, test := range *testsuite {
|
||||
v := w.NewFile()
|
||||
defer func() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}()
|
||||
|
||||
e := v.BeginEdit()
|
||||
v.Insert(e, 0, test.in)
|
||||
v.EndEdit(e)
|
||||
|
||||
v.Sel().Clear()
|
||||
if test.in_region != nil {
|
||||
for _, r := range test.in_region {
|
||||
v.Sel().Add(r)
|
||||
}
|
||||
}
|
||||
ed.CommandHandler().RunTextCommand(v, command, nil)
|
||||
sr := v.Buffer().Substr(Region{0, v.Buffer().Size()})
|
||||
if sr != test.exp {
|
||||
t.Errorf("%s test %d failed: %v, %+v", command, i, sr, test)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTitleCase(t *testing.T) {
|
||||
tests := []caseTest{
|
||||
/*single selection*/
|
||||
{
|
||||
// Please note the bizarre capitalization of the first L in he'Ll... This is due to a bug in go's strings
|
||||
// library. I'm going to try to get them to fix it... If not, maybe we'll have
|
||||
// to write our own Title Casing function.
|
||||
[]Region{{24, 51}},
|
||||
|
||||
"Give a man a match, and he'll be warm for a minute, but set him on fire, and he'll be warm for the rest of his life.",
|
||||
"Give a man a match, and He'Ll Be Warm For A Minute, but set him on fire, and he'll be warm for the rest of his life.",
|
||||
},
|
||||
/*multiple selection*/
|
||||
{
|
||||
[]Region{{0, 17}, {52, 71}},
|
||||
|
||||
"Give a man a match, and he'll be warm for a minute, but set him on fire, and he'll be warm for the rest of his life.",
|
||||
"Give A Man A Match, and he'll be warm for a minute, But Set Him On Fire, and he'll be warm for the rest of his life.",
|
||||
},
|
||||
/*no selection*/
|
||||
{
|
||||
nil,
|
||||
|
||||
"Give a man a match, and he'll be warm for a minute, but set him on fire, and he'll be warm for the rest of his life.",
|
||||
"Give a man a match, and he'll be warm for a minute, but set him on fire, and he'll be warm for the rest of his life.",
|
||||
},
|
||||
/*unicode*/
|
||||
{
|
||||
[]Region{{0, 12}},
|
||||
|
||||
"ничего себе!",
|
||||
"Ничего Себе!",
|
||||
},
|
||||
/*asian characters*/
|
||||
{
|
||||
[]Region{{0, 9}},
|
||||
|
||||
"千里之行﹐始于足下",
|
||||
"千里之行﹐始于足下",
|
||||
},
|
||||
}
|
||||
|
||||
runCaseTest("title_case", &tests, t)
|
||||
}
|
||||
|
||||
func TestSwapCase(t *testing.T) {
|
||||
tests := []caseTest{
|
||||
{
|
||||
[]Region{{0, 0}},
|
||||
|
||||
"",
|
||||
"",
|
||||
},
|
||||
{
|
||||
[]Region{{0, 13}},
|
||||
|
||||
"Hello, World!",
|
||||
"hELLO, wORLD!",
|
||||
},
|
||||
{
|
||||
[]Region{{0, 11}},
|
||||
|
||||
"ПрИвЕт, МиР",
|
||||
"пРиВеТ, мИр",
|
||||
},
|
||||
}
|
||||
|
||||
runCaseTest("swap_case", &tests, t)
|
||||
}
|
||||
|
||||
func TestUpperCase(t *testing.T) {
|
||||
tests := []caseTest{
|
||||
/*single selection*/
|
||||
{
|
||||
[]Region{{0, 76}},
|
||||
|
||||
"Try not to become a man of success, but rather try to become a man of value.",
|
||||
"TRY NOT TO BECOME A MAN OF SUCCESS, BUT RATHER TRY TO BECOME A MAN OF VALUE.",
|
||||
},
|
||||
/*multiple selection*/
|
||||
{
|
||||
[]Region{{0, 20}, {74, 76}},
|
||||
|
||||
"Try not to become a man of success, but rather try to become a man of value.",
|
||||
"TRY NOT TO BECOME A man of success, but rather try to become a man of valuE.",
|
||||
},
|
||||
/*no selection*/
|
||||
{
|
||||
nil,
|
||||
|
||||
"Try not to become a man of success, but rather try to become a man of value.",
|
||||
"Try not to become a man of success, but rather try to become a man of value.",
|
||||
},
|
||||
/*unicode*/
|
||||
{
|
||||
[]Region{{0, 74}},
|
||||
|
||||
"чем больше законов и постановлений, тем больше разбойников и преступлений!",
|
||||
"ЧЕМ БОЛЬШЕ ЗАКОНОВ И ПОСТАНОВЛЕНИЙ, ТЕМ БОЛЬШЕ РАЗБОЙНИКОВ И ПРЕСТУПЛЕНИЙ!",
|
||||
},
|
||||
/*asian characters*/
|
||||
{
|
||||
[]Region{{0, 9}},
|
||||
|
||||
"千里之行﹐始于足下",
|
||||
"千里之行﹐始于足下",
|
||||
},
|
||||
}
|
||||
|
||||
runCaseTest("upper_case", &tests, t)
|
||||
}
|
||||
|
||||
func TestLowerCase(t *testing.T) {
|
||||
tests := []caseTest{
|
||||
/*single selection*/
|
||||
{
|
||||
[]Region{{0, 76}},
|
||||
|
||||
"TRY NOT TO BECOME A MAN OF SUCCESS, BUT RATHER TRY TO BECOME A MAN OF VALUE.",
|
||||
"try not to become a man of success, but rather try to become a man of value.",
|
||||
},
|
||||
/*multiple selection*/
|
||||
{
|
||||
[]Region{{0, 20}, {74, 76}},
|
||||
|
||||
"TRY NOT TO BECOME A MAN OF SUCCESS, BUT RATHER TRY TO BECOME A MAN OF VALUE.",
|
||||
"try not to become a MAN OF SUCCESS, BUT RATHER TRY TO BECOME A MAN OF VALUe.",
|
||||
},
|
||||
/*no selection*/
|
||||
{
|
||||
nil,
|
||||
|
||||
"Try not to become a man of success, but rather try to become a man of value.",
|
||||
"Try not to become a man of success, but rather try to become a man of value.",
|
||||
},
|
||||
/*unicode*/
|
||||
{
|
||||
[]Region{{0, 74}},
|
||||
|
||||
"ЧЕМ БОЛЬШЕ ЗАКОНОВ И ПОСТАНОВЛЕНИЙ, ТЕМ БОЛЬШЕ РАЗБОЙНИКОВ И ПРЕСТУПЛЕНИЙ!",
|
||||
"чем больше законов и постановлений, тем больше разбойников и преступлений!",
|
||||
},
|
||||
/*asian characters*/
|
||||
{
|
||||
[]Region{{0, 9}},
|
||||
|
||||
"千里之行﹐始于足下",
|
||||
"千里之行﹐始于足下",
|
||||
},
|
||||
}
|
||||
|
||||
runCaseTest("lower_case", &tests, t)
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
// Copyright 2015 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type (
|
||||
ToggleCommentCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
)
|
||||
|
||||
func (c *ToggleCommentCommand) Run(v *View, e *Edit) error {
|
||||
// TODO: Comment the line if we only have a cursor.
|
||||
// TODO: Expand the selection after altering it.
|
||||
// TODO: Align the comment characters for multiline selections.
|
||||
// TODO: Get the comment value from the Textmate files.
|
||||
comm := "//"
|
||||
|
||||
for _, r := range v.Sel().Regions() {
|
||||
if r.Size() != 0 {
|
||||
t := v.Buffer().Substr(r)
|
||||
|
||||
trim := strings.TrimLeftFunc(t, unicode.IsSpace)
|
||||
if strings.HasPrefix(trim, comm) {
|
||||
repl := comm
|
||||
if strings.HasPrefix(trim, comm+" ") {
|
||||
repl += " "
|
||||
}
|
||||
|
||||
t = strings.Replace(t, repl, "", 1)
|
||||
} else {
|
||||
t = strings.Replace(t, trim, comm+" "+trim, 1)
|
||||
}
|
||||
|
||||
v.Replace(e, r, t)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
register([]Command{
|
||||
&ToggleCommentCommand{},
|
||||
})
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
// Copyright 2015 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
"github.com/limetext/text"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestToggleComment(t *testing.T) {
|
||||
tests := []struct {
|
||||
r []text.Region
|
||||
in string
|
||||
exp string
|
||||
}{
|
||||
{
|
||||
[]text.Region{{0, 3}},
|
||||
|
||||
"test",
|
||||
"// test",
|
||||
},
|
||||
{
|
||||
[]text.Region{{0, 6}},
|
||||
|
||||
"// test",
|
||||
"test",
|
||||
},
|
||||
{
|
||||
[]text.Region{{0, 5}},
|
||||
|
||||
"//test",
|
||||
"test",
|
||||
},
|
||||
{
|
||||
[]text.Region{{0, 8}},
|
||||
|
||||
"// test",
|
||||
" test",
|
||||
},
|
||||
{
|
||||
[]text.Region{{0, 7}},
|
||||
|
||||
" test",
|
||||
" // test",
|
||||
},
|
||||
{
|
||||
[]text.Region{{0, 10}},
|
||||
|
||||
" // test",
|
||||
" test",
|
||||
},
|
||||
{
|
||||
[]text.Region{{0, 9}},
|
||||
|
||||
" //test",
|
||||
" test",
|
||||
},
|
||||
{
|
||||
[]text.Region{{0, 12}},
|
||||
|
||||
" // test",
|
||||
" test",
|
||||
},
|
||||
{
|
||||
[]text.Region{{0, 8}},
|
||||
|
||||
"\t test",
|
||||
"\t // test",
|
||||
},
|
||||
{
|
||||
[]text.Region{{0, 11}},
|
||||
|
||||
"\t // test",
|
||||
"\t test",
|
||||
},
|
||||
{
|
||||
[]text.Region{{0, 10}},
|
||||
|
||||
"\t //test",
|
||||
"\t test",
|
||||
},
|
||||
{
|
||||
[]text.Region{{0, 13}},
|
||||
|
||||
"\t // test",
|
||||
"\t test",
|
||||
},
|
||||
}
|
||||
|
||||
ed := GetEditor()
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
for i, test := range tests {
|
||||
v := w.NewFile()
|
||||
defer func() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}()
|
||||
|
||||
e := v.BeginEdit()
|
||||
v.Insert(e, 0, test.in)
|
||||
v.EndEdit(e)
|
||||
|
||||
v.Sel().Clear()
|
||||
if test.r != nil {
|
||||
for _, r := range test.r {
|
||||
v.Sel().Add(r)
|
||||
}
|
||||
}
|
||||
ed.CommandHandler().RunTextCommand(v, "toggle_comment", nil)
|
||||
sr := v.Buffer().Substr(text.Region{0, v.Buffer().Size()})
|
||||
if sr != test.exp {
|
||||
t.Errorf("%s test %d failed: %v, %+v", "toggle_comment", i, sr, test)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
"github.com/limetext/text"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
CopyCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
|
||||
CutCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
|
||||
PasteCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
)
|
||||
|
||||
func getRegions(v *View, cut bool) *text.RegionSet {
|
||||
rs := &text.RegionSet{}
|
||||
regions := v.Sel().Regions()
|
||||
sort.Sort(regionSorter(regions))
|
||||
rs.AddAll(regions)
|
||||
|
||||
he, ae := rs.HasEmpty(), !rs.HasNonEmpty() || cut
|
||||
for _, r := range rs.Regions() {
|
||||
if ae && r.Empty() {
|
||||
rs.Add(v.Buffer().FullLineR(r))
|
||||
} else if he && r.Empty() {
|
||||
rs.Substract(r)
|
||||
}
|
||||
}
|
||||
|
||||
return rs
|
||||
}
|
||||
|
||||
func getSelSubstrs(v *View, rs *text.RegionSet) []string {
|
||||
var add, s1 string
|
||||
s := make([]string, len(rs.Regions()))
|
||||
for i, r := range rs.Regions() {
|
||||
add = ""
|
||||
s1 = v.Buffer().Substr(r)
|
||||
if !v.Sel().HasNonEmpty() && !strings.HasSuffix(s1, "\n") {
|
||||
add = "\n"
|
||||
}
|
||||
s[i] = s1 + add
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (c *CopyCommand) Run(v *View, e *Edit) error {
|
||||
rs := getRegions(v, false)
|
||||
s := getSelSubstrs(v, rs)
|
||||
|
||||
GetEditor().SetClipboard(strings.Join(s, "\n"))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CutCommand) Run(v *View, e *Edit) error {
|
||||
s := getSelSubstrs(v, getRegions(v, false))
|
||||
|
||||
rs := getRegions(v, true)
|
||||
regions := rs.Regions()
|
||||
sort.Sort(sort.Reverse(regionSorter(regions)))
|
||||
for _, r := range regions {
|
||||
v.Erase(e, r)
|
||||
}
|
||||
|
||||
GetEditor().SetClipboard(strings.Join(s, "\n"))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *PasteCommand) Run(v *View, e *Edit) error {
|
||||
// TODO: Paste the entire line on the line before the cursor if a
|
||||
// line was autocopied.
|
||||
|
||||
ed := GetEditor()
|
||||
|
||||
rs := &text.RegionSet{}
|
||||
regions := v.Sel().Regions()
|
||||
sort.Sort(sort.Reverse(regionSorter(regions)))
|
||||
rs.AddAll(regions)
|
||||
for _, r := range rs.Regions() {
|
||||
v.Replace(e, r, ed.GetClipboard())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
register([]Command{
|
||||
&CopyCommand{},
|
||||
&CutCommand{},
|
||||
&PasteCommand{},
|
||||
})
|
||||
}
|
@ -1,267 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
"github.com/limetext/text"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type copyTest struct {
|
||||
buf string
|
||||
clip string
|
||||
regions []text.Region
|
||||
expClip string
|
||||
expBuf string
|
||||
}
|
||||
|
||||
var dummyClipboard string
|
||||
|
||||
func runCopyTest(command string, tests *[]copyTest, t *testing.T) {
|
||||
ed := GetEditor()
|
||||
ed.SetClipboardFuncs(func(n string) (err error) {
|
||||
dummyClipboard = n
|
||||
return nil
|
||||
}, func() (string, error) {
|
||||
return dummyClipboard, nil
|
||||
})
|
||||
defer ed.Init()
|
||||
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
for i, test := range *tests {
|
||||
v := w.NewFile()
|
||||
defer func() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}()
|
||||
|
||||
v.Buffer().Insert(0, test.buf)
|
||||
v.Sel().Clear()
|
||||
|
||||
ed.SetClipboard(test.clip)
|
||||
|
||||
for _, r := range test.regions {
|
||||
v.Sel().Add(r)
|
||||
}
|
||||
|
||||
ed.CommandHandler().RunTextCommand(v, command, nil)
|
||||
|
||||
if ed.GetClipboard() != test.expClip {
|
||||
t.Errorf("Test %d: Expected clipboard to be %q, but got %q", i, test.expClip, ed.GetClipboard())
|
||||
}
|
||||
|
||||
b := v.Buffer().Substr(text.Region{A: 0, B: v.Buffer().Size()})
|
||||
|
||||
if b != test.expBuf {
|
||||
t.Errorf("Test %d: Expected buffer to be %q, but got %q", i, test.expBuf, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopy(t *testing.T) {
|
||||
tests := []copyTest{
|
||||
{
|
||||
"test string",
|
||||
"",
|
||||
[]text.Region{{1, 3}},
|
||||
"es",
|
||||
"test string",
|
||||
},
|
||||
{
|
||||
"test\nstring",
|
||||
"",
|
||||
[]text.Region{{3, 6}},
|
||||
"t\ns",
|
||||
"test\nstring",
|
||||
},
|
||||
{
|
||||
"test string",
|
||||
"",
|
||||
[]text.Region{{3, 3}},
|
||||
"test string\n",
|
||||
"test string",
|
||||
},
|
||||
{
|
||||
"test string",
|
||||
"",
|
||||
[]text.Region{{1, 3}, {5, 6}},
|
||||
"es\ns",
|
||||
"test string",
|
||||
},
|
||||
{
|
||||
"test\nstring",
|
||||
"",
|
||||
[]text.Region{{1, 3}, {5, 6}},
|
||||
"es\ns",
|
||||
"test\nstring",
|
||||
},
|
||||
{
|
||||
"test\nstring",
|
||||
"",
|
||||
[]text.Region{{1, 1}, {7, 7}},
|
||||
"test\n\nstring\n",
|
||||
"test\nstring",
|
||||
},
|
||||
{
|
||||
"test\nstring",
|
||||
"",
|
||||
[]text.Region{{3, 6}, {9, 10}},
|
||||
"t\ns\nn",
|
||||
"test\nstring",
|
||||
},
|
||||
{
|
||||
"test string",
|
||||
"",
|
||||
[]text.Region{{5, 6}, {1, 3}},
|
||||
"es\ns",
|
||||
"test string",
|
||||
},
|
||||
{
|
||||
"test string",
|
||||
"",
|
||||
[]text.Region{{1, 1}, {6, 7}},
|
||||
"t",
|
||||
"test string",
|
||||
},
|
||||
}
|
||||
|
||||
runCopyTest("copy", &tests, t)
|
||||
}
|
||||
|
||||
func TestCut(t *testing.T) {
|
||||
tests := []copyTest{
|
||||
{
|
||||
"test string",
|
||||
"",
|
||||
[]text.Region{{1, 3}},
|
||||
"es",
|
||||
"tt string",
|
||||
},
|
||||
{
|
||||
"test\nstring",
|
||||
"",
|
||||
[]text.Region{{3, 6}},
|
||||
"t\ns",
|
||||
"testring",
|
||||
},
|
||||
{
|
||||
"test string",
|
||||
"",
|
||||
[]text.Region{{3, 3}},
|
||||
"test string\n",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"test string",
|
||||
"",
|
||||
[]text.Region{{5, 6}, {1, 3}},
|
||||
"es\ns",
|
||||
"tt tring",
|
||||
},
|
||||
{
|
||||
"test\nstring",
|
||||
"",
|
||||
[]text.Region{{1, 3}, {5, 6}},
|
||||
"es\ns",
|
||||
"tt\ntring",
|
||||
},
|
||||
{
|
||||
"test\nstring",
|
||||
"",
|
||||
[]text.Region{{1, 1}, {7, 7}},
|
||||
"test\n\nstring\n",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"test\nstring",
|
||||
"",
|
||||
[]text.Region{{3, 6}, {9, 10}},
|
||||
"t\ns\nn",
|
||||
"testrig",
|
||||
},
|
||||
{
|
||||
"test string",
|
||||
"",
|
||||
[]text.Region{{5, 6}, {1, 3}},
|
||||
"es\ns",
|
||||
"tt tring",
|
||||
},
|
||||
{
|
||||
"test string",
|
||||
"",
|
||||
[]text.Region{{6, 7}, {1, 1}},
|
||||
"t",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"test\nstring",
|
||||
"",
|
||||
[]text.Region{{1, 1}, {6, 7}},
|
||||
"t",
|
||||
"sring",
|
||||
},
|
||||
}
|
||||
|
||||
runCopyTest("cut", &tests, t)
|
||||
}
|
||||
|
||||
func TestPaste(t *testing.T) {
|
||||
tests := []copyTest{
|
||||
{
|
||||
"test string",
|
||||
"test",
|
||||
[]text.Region{{1, 1}},
|
||||
"test",
|
||||
"ttestest string",
|
||||
},
|
||||
{
|
||||
"test string",
|
||||
"test",
|
||||
[]text.Region{{1, 3}},
|
||||
"test",
|
||||
"ttestt string",
|
||||
},
|
||||
{
|
||||
"test\nstring",
|
||||
"test",
|
||||
[]text.Region{{3, 6}},
|
||||
"test",
|
||||
"testesttring",
|
||||
},
|
||||
{
|
||||
"test string",
|
||||
"test",
|
||||
[]text.Region{{1, 3}, {5, 6}},
|
||||
"test",
|
||||
"ttestt testtring",
|
||||
},
|
||||
{
|
||||
"test\nstring",
|
||||
"test",
|
||||
[]text.Region{{1, 3}, {5, 6}},
|
||||
"test",
|
||||
"ttestt\ntesttring",
|
||||
},
|
||||
{
|
||||
"test\nstring",
|
||||
"test",
|
||||
[]text.Region{{3, 6}, {9, 10}},
|
||||
"test",
|
||||
"testesttritestg",
|
||||
},
|
||||
{
|
||||
"test\nstring",
|
||||
"test",
|
||||
[]text.Region{{9, 10}, {3, 6}},
|
||||
"test",
|
||||
"testesttritestg",
|
||||
},
|
||||
}
|
||||
|
||||
runCopyTest("paste", &tests, t)
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
// This package implements a couple of standard commands
|
||||
// available to the editor.
|
||||
//
|
||||
// Typically the package is imported by a frontend via
|
||||
// import (
|
||||
// _ "github.com/limetext/lime/backend/commands"
|
||||
// )
|
||||
//
|
||||
// See the wiki page for more details on implementing commands:
|
||||
// https://github.com/limetext/lime/wiki/Implementing-commands
|
||||
package commands
|
@ -1,38 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
)
|
||||
|
||||
type (
|
||||
NewFileCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
|
||||
OpenFileCommand struct {
|
||||
DefaultCommand
|
||||
Path string
|
||||
}
|
||||
)
|
||||
|
||||
func (c *NewFileCommand) Run(w *Window) error {
|
||||
ed := GetEditor()
|
||||
ed.ActiveWindow().NewFile()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OpenFileCommand) Run(w *Window) error {
|
||||
w.OpenFile(o.Path, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
register([]Command{
|
||||
&NewFileCommand{},
|
||||
&OpenFileCommand{},
|
||||
})
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewFile(t *testing.T) {
|
||||
ed := GetEditor()
|
||||
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
l := len(w.Views())
|
||||
|
||||
ed.CommandHandler().RunWindowCommand(w, "new_file", nil)
|
||||
|
||||
if len(w.Views()) != l+1 {
|
||||
t.Errorf("Expected %d views, but got %d", l+1, len(w.Views()))
|
||||
}
|
||||
|
||||
for _, v := range w.Views() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenFile(t *testing.T) {
|
||||
ed := GetEditor()
|
||||
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
l := len(w.Views())
|
||||
|
||||
testPath := "open_file_test.go"
|
||||
ed.CommandHandler().RunWindowCommand(w, "open_file", Args{"path": testPath})
|
||||
|
||||
if len(w.Views()) != l+1 {
|
||||
t.Errorf("Expected %d views, but got %d", l+1, len(w.Views()))
|
||||
}
|
||||
exp, err := filepath.Abs(testPath)
|
||||
if err != nil {
|
||||
exp = testPath
|
||||
}
|
||||
if w.Views()[l].Buffer().FileName() != exp {
|
||||
t.Errorf("Expected %s as FileName, but got %s", testPath, w.Views()[l].Buffer().FileName())
|
||||
}
|
||||
|
||||
for _, v := range w.Views() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}
|
||||
}
|
@ -1,140 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
. "github.com/limetext/lime/backend"
|
||||
. "github.com/limetext/text"
|
||||
)
|
||||
|
||||
type (
|
||||
// The FindUnderExpandCommand extends the selection to the current word
|
||||
// if the current selection region is empty.
|
||||
// If one character or more is selected, the text buffer is scanned for
|
||||
// the next occurrence of the selection and that region too is added to
|
||||
// the selection set.
|
||||
FindUnderExpandCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
// The FindNext command searches for the last search term, starting at
|
||||
// the end of the last selection in the buffer, and wrapping around. If
|
||||
// it finds the term, it clears the current selections and selects the
|
||||
// newly-found regions.
|
||||
FindNextCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
|
||||
// The ReplaceNextCommand searches for the "old" argument text,
|
||||
// and at the first occurance of the text, replaces it with the
|
||||
// "new" argument text. If there are multiple regions, the find
|
||||
// starts from the max region.
|
||||
ReplaceNextCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// Remembers the last sequence of runes searched for.
|
||||
lastSearch []rune
|
||||
replaceText string
|
||||
)
|
||||
|
||||
func (c *FindUnderExpandCommand) Run(v *View, e *Edit) error {
|
||||
sel := v.Sel()
|
||||
rs := sel.Regions()
|
||||
|
||||
if sel.HasEmpty() {
|
||||
for i, r := range rs {
|
||||
if r2 := v.Buffer().Word(r.A); r2.Size() > r.Size() {
|
||||
rs[i] = r2
|
||||
}
|
||||
}
|
||||
sel.Clear()
|
||||
sel.AddAll(rs)
|
||||
b := v.Buffer()
|
||||
lastSearch = b.SubstrR(rs[len(rs)-1])
|
||||
return nil
|
||||
}
|
||||
last := rs[len(rs)-1]
|
||||
b := v.Buffer()
|
||||
lastSearch = b.SubstrR(last)
|
||||
r := v.Find(string(lastSearch), last.End(), IGNORECASE|LITERAL)
|
||||
if r.A != -1 {
|
||||
sel.Add(r)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func nextSelection(v *View, search string) (Region, error) {
|
||||
sel := v.Sel()
|
||||
rs := sel.Regions()
|
||||
last := 0
|
||||
wrap, _ := v.Settings().Get("find_wrap").(bool)
|
||||
|
||||
// Regions are not sorted, so finding the last one requires a search.
|
||||
for _, r := range rs {
|
||||
last = Max(last, r.End())
|
||||
}
|
||||
|
||||
// Start the search right after the last selection.
|
||||
start := last
|
||||
r := v.Find(search, start, IGNORECASE|LITERAL)
|
||||
// If not found yet and find_wrap setting is true, search
|
||||
// from the start of the buffer to our original starting point.
|
||||
if r.A == -1 && wrap {
|
||||
r = v.Find(search, 0, IGNORECASE|LITERAL)
|
||||
}
|
||||
// If we found our string, select it.
|
||||
if r.A != -1 {
|
||||
return r, nil
|
||||
}
|
||||
return Region{-1, -1}, errors.New("Selection not Found")
|
||||
}
|
||||
|
||||
func (c *FindNextCommand) Run(v *View, e *Edit) error {
|
||||
/*
|
||||
Correct behavior of FindNext:
|
||||
- If there is no previous search, do nothing
|
||||
- Find the last region in the buffer, start the
|
||||
search immediately after that.
|
||||
- If the search term is found, clear any existing
|
||||
selections, and select the newly-found region.
|
||||
- Right now this is doing a case-sensitive search. In ST3
|
||||
that's a setting.
|
||||
*/
|
||||
|
||||
// If there is no last search term, nothing to do here.
|
||||
if len(lastSearch) == 0 {
|
||||
return nil
|
||||
}
|
||||
newr, err := nextSelection(v, string(lastSearch))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sel := v.Sel()
|
||||
sel.Clear()
|
||||
sel.Add(newr)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ReplaceNextCommand) Run(v *View, e *Edit) error {
|
||||
// use selection function from find.go to get the next region
|
||||
selection, err := nextSelection(v, string(lastSearch))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Erase(e, selection)
|
||||
v.Insert(e, selection.Begin(), replaceText)
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
register([]Command{
|
||||
&FindUnderExpandCommand{},
|
||||
&FindNextCommand{},
|
||||
&ReplaceNextCommand{},
|
||||
})
|
||||
}
|
@ -1,193 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
. "github.com/limetext/text"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type findTest struct {
|
||||
text string
|
||||
in []Region
|
||||
exp []Region
|
||||
fw bool
|
||||
}
|
||||
|
||||
func runFindTest(tests []findTest, t *testing.T, commands ...string) {
|
||||
ed := GetEditor()
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
v := w.NewFile()
|
||||
defer func() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}()
|
||||
|
||||
for i, test := range tests {
|
||||
e := v.BeginEdit()
|
||||
v.Insert(e, 0, test.text)
|
||||
v.EndEdit(e)
|
||||
v.Sel().Clear()
|
||||
for _, r := range test.in {
|
||||
v.Sel().Add(r)
|
||||
}
|
||||
|
||||
v.Settings().Set("find_wrap", test.fw)
|
||||
|
||||
for _, command := range commands {
|
||||
ed.CommandHandler().RunTextCommand(v, command, nil)
|
||||
}
|
||||
if sr := v.Sel().Regions(); !reflect.DeepEqual(sr, test.exp) {
|
||||
t.Errorf("Test %d: Expected %s, but got %s", i, test.exp, sr)
|
||||
}
|
||||
e = v.BeginEdit()
|
||||
v.Erase(e, Region{0, v.Buffer().Size()})
|
||||
v.EndEdit(e)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindUnderExpand(t *testing.T) {
|
||||
tests := []findTest{
|
||||
{
|
||||
"Hello World!\nTest123123\nAbrakadabra\n",
|
||||
[]Region{{0, 0}},
|
||||
[]Region{{0, 5}},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Hello World!\nTest123123\nAbrakadabra\n",
|
||||
[]Region{{19, 20}},
|
||||
[]Region{{19, 20}, {22, 23}},
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
runFindTest(tests, t, "find_under_expand")
|
||||
}
|
||||
|
||||
func TestFindNext(t *testing.T) {
|
||||
tests := []findTest{
|
||||
{
|
||||
"Hello World!\nTest123123\nAbrakadabra\n",
|
||||
[]Region{{17, 20}},
|
||||
[]Region{{17, 20}},
|
||||
true,
|
||||
},
|
||||
// test find_wrap setting true
|
||||
{
|
||||
"Hello World!\nTest123123\nAbrakadabra\n",
|
||||
[]Region{{21, 23}},
|
||||
[]Region{{18, 20}},
|
||||
true,
|
||||
},
|
||||
// test find_wrap setting false
|
||||
{
|
||||
"Hello World!\nTest123123\nAbrakadabra\n",
|
||||
[]Region{{21, 23}},
|
||||
[]Region{{21, 23}},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
runFindTest(tests, t, "find_under_expand", "find_next")
|
||||
}
|
||||
|
||||
type replaceTest struct {
|
||||
cursors []Region
|
||||
in string
|
||||
exp string
|
||||
fw bool
|
||||
}
|
||||
|
||||
func runReplaceTest(tests []replaceTest, t *testing.T, commands ...string) {
|
||||
ed := GetEditor()
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
v := w.NewFile()
|
||||
defer func() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}()
|
||||
|
||||
for i, test := range tests {
|
||||
e := v.BeginEdit()
|
||||
v.Insert(e, 0, test.in)
|
||||
v.EndEdit(e)
|
||||
v.Sel().Clear()
|
||||
|
||||
for _, r := range test.cursors {
|
||||
v.Sel().Add(r)
|
||||
}
|
||||
|
||||
v.Settings().Set("find_wrap", test.fw)
|
||||
|
||||
replaceText = "f"
|
||||
for _, command := range commands {
|
||||
ed.CommandHandler().RunTextCommand(v, command, nil)
|
||||
}
|
||||
if out := v.Buffer().Substr(Region{0, v.Buffer().Size()}); out != test.exp {
|
||||
t.Errorf("Test %d failed: %s, %+v", i, out, test)
|
||||
}
|
||||
e = v.BeginEdit()
|
||||
v.Erase(e, Region{0, v.Buffer().Size()})
|
||||
v.EndEdit(e)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplaceNext(t *testing.T) {
|
||||
tests := []replaceTest{
|
||||
{
|
||||
[]Region{{1, 1}, {2, 2}, {3, 3}},
|
||||
"abc abc bac abc abc",
|
||||
"abc f bac abc abc",
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]Region{{0, 0}, {4, 4}, {8, 8}, {12, 13}},
|
||||
"abc abc bac abc abc",
|
||||
"abc abc bac abc f",
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]Region{{12, 13}, {8, 8}, {4, 4}, {1, 0}},
|
||||
"abc abc bac abc abc",
|
||||
"abc abc bac abc f",
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]Region{{15, 15}},
|
||||
"abc abc bac abc abc",
|
||||
"abc abc bac abc f",
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]Region{{0, 0}},
|
||||
"abc abc bac abc abc",
|
||||
"abc f bac abc abc",
|
||||
true,
|
||||
},
|
||||
// test find_wrap setting true
|
||||
{
|
||||
[]Region{{16, 19}},
|
||||
"abc abc bac abc abc",
|
||||
"f abc bac abc abc",
|
||||
true,
|
||||
},
|
||||
// test find_wrap setting false
|
||||
{
|
||||
[]Region{{16, 19}},
|
||||
"abc abc bac abc abc",
|
||||
"abc abc bac abc abc",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
runReplaceTest(tests, t, "find_under_expand", "replace_next")
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "github.com/limetext/lime/backend"
|
||||
)
|
||||
|
||||
const lime_cmd_mark = "lime.cmd.mark"
|
||||
|
||||
type (
|
||||
// The MarkUndoGroupsForGluingCommand marks the current position
|
||||
// in the undo stack as the start of commands to glue, potentially
|
||||
// overwriting any existing marks.
|
||||
MarkUndoGroupsForGluingCommand struct {
|
||||
BypassUndoCommand
|
||||
}
|
||||
|
||||
// The GlueMarkedUndoGroupsCommand merges commands from the previously
|
||||
// marked undo stack location to the current location into a single
|
||||
// entry in the undo stack.
|
||||
GlueMarkedUndoGroupsCommand struct {
|
||||
BypassUndoCommand
|
||||
}
|
||||
|
||||
// The MaybeMarkUndoGroupsForGluingCommand is similar to
|
||||
// MarkUndoGroupsForGluingCommand with the exception that if there
|
||||
// is already a mark set, it is not overwritten.
|
||||
MaybeMarkUndoGroupsForGluingCommand struct {
|
||||
BypassUndoCommand
|
||||
}
|
||||
|
||||
// The UnmarkUndoGroupsForGluingCommand removes the glue mark set by
|
||||
// either MarkUndoGroupsForGluingCommand or MaybeMarkUndoGroupsForGluingCommand
|
||||
// if it was set.
|
||||
UnmarkUndoGroupsForGluingCommand struct {
|
||||
BypassUndoCommand
|
||||
}
|
||||
)
|
||||
|
||||
func (c *MarkUndoGroupsForGluingCommand) Run(v *View, e *Edit) error {
|
||||
v.Settings().Set(lime_cmd_mark, v.UndoStack().Position())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *UnmarkUndoGroupsForGluingCommand) Run(v *View, e *Edit) error {
|
||||
v.Settings().Erase(lime_cmd_mark)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *GlueMarkedUndoGroupsCommand) Run(v *View, e *Edit) error {
|
||||
pos := v.UndoStack().Position()
|
||||
mark, ok := v.Settings().Get(lime_cmd_mark).(int)
|
||||
if !ok {
|
||||
return fmt.Errorf("No mark in the current view")
|
||||
}
|
||||
if l, p := pos-mark, mark; p != -1 && (l-p) > 1 {
|
||||
v.UndoStack().GlueFrom(mark)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MaybeMarkUndoGroupsForGluingCommand) Run(v *View, e *Edit) error {
|
||||
if !v.Settings().Has(lime_cmd_mark) {
|
||||
v.Settings().Set(lime_cmd_mark, v.UndoStack().Position())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
register([]Command{
|
||||
&MarkUndoGroupsForGluingCommand{},
|
||||
&GlueMarkedUndoGroupsCommand{},
|
||||
&MaybeMarkUndoGroupsForGluingCommand{},
|
||||
&UnmarkUndoGroupsForGluingCommand{},
|
||||
})
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
"github.com/limetext/text"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGlueCmds(t *testing.T) {
|
||||
ed := GetEditor()
|
||||
ch := ed.CommandHandler()
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
v := w.NewFile()
|
||||
defer func() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}()
|
||||
|
||||
v.SetScratch(true)
|
||||
e := v.BeginEdit()
|
||||
v.Insert(e, 0, "Hello World!\nTest123123\nAbrakadabra\n")
|
||||
v.EndEdit(e)
|
||||
v.SetScratch(false)
|
||||
ch.RunTextCommand(v, "mark_undo_groups_for_gluing", nil)
|
||||
ch.RunTextCommand(v, "insert", Args{"characters": "a"})
|
||||
ch.RunTextCommand(v, "insert", Args{"characters": "b"})
|
||||
ch.RunTextCommand(v, "insert", Args{"characters": "c"})
|
||||
ch.RunTextCommand(v, "glue_marked_undo_groups", nil)
|
||||
if v.UndoStack().Position() != 1 {
|
||||
t.Error(v.UndoStack().Position())
|
||||
} else if d := v.Buffer().Substr(text.Region{A: 0, B: v.Buffer().Size()}); d != "Hello World!\nTest123123\nAbrakadabra\nabc" {
|
||||
t.Error(d)
|
||||
}
|
||||
ch.RunTextCommand(v, "undo", nil)
|
||||
if d := v.Buffer().Substr(text.Region{A: 0, B: v.Buffer().Size()}); d != "Hello World!\nTest123123\nAbrakadabra\n" {
|
||||
t.Error(d)
|
||||
}
|
||||
ch.RunTextCommand(v, "redo", nil)
|
||||
if d := v.Buffer().Substr(text.Region{A: 0, B: v.Buffer().Size()}); d != "Hello World!\nTest123123\nAbrakadabra\nabc" {
|
||||
t.Error(d)
|
||||
}
|
||||
if v.UndoStack().Position() != 1 {
|
||||
t.Error(v.UndoStack().Position())
|
||||
} else if d := v.Buffer().Substr(text.Region{A: 0, B: v.Buffer().Size()}); d != "Hello World!\nTest123123\nAbrakadabra\nabc" {
|
||||
t.Error(d)
|
||||
}
|
||||
ch.RunTextCommand(v, "undo", nil)
|
||||
if d := v.Buffer().Substr(text.Region{A: 0, B: v.Buffer().Size()}); d != "Hello World!\nTest123123\nAbrakadabra\n" {
|
||||
t.Error(d)
|
||||
}
|
||||
|
||||
ch.RunTextCommand(v, "maybe_mark_undo_groups_for_gluing", nil)
|
||||
ch.RunTextCommand(v, "insert", Args{"characters": "a"})
|
||||
ch.RunTextCommand(v, "maybe_mark_undo_groups_for_gluing", nil)
|
||||
ch.RunTextCommand(v, "insert", Args{"characters": "b"})
|
||||
ch.RunTextCommand(v, "maybe_mark_undo_groups_for_gluing", nil)
|
||||
ch.RunTextCommand(v, "insert", Args{"characters": "c"})
|
||||
ch.RunTextCommand(v, "maybe_mark_undo_groups_for_gluing", nil)
|
||||
ch.RunTextCommand(v, "glue_marked_undo_groups", nil)
|
||||
if v.UndoStack().Position() != 1 {
|
||||
t.Error(v.UndoStack().Position())
|
||||
} else if d := v.Buffer().Substr(text.Region{A: 0, B: v.Buffer().Size()}); d != "Hello World!\nTest123123\nAbrakadabra\nabc" {
|
||||
t.Error(d)
|
||||
}
|
||||
ch.RunTextCommand(v, "undo", nil)
|
||||
if d := v.Buffer().Substr(text.Region{A: 0, B: v.Buffer().Size()}); d != "Hello World!\nTest123123\nAbrakadabra\n" {
|
||||
t.Error(d)
|
||||
}
|
||||
ch.RunTextCommand(v, "redo", nil)
|
||||
if d := v.Buffer().Substr(text.Region{A: 0, B: v.Buffer().Size()}); d != "Hello World!\nTest123123\nAbrakadabra\nabc" {
|
||||
t.Error(d)
|
||||
}
|
||||
if v.UndoStack().Position() != 1 {
|
||||
t.Error(v.UndoStack().Position())
|
||||
} else if d := v.Buffer().Substr(text.Region{A: 0, B: v.Buffer().Size()}); d != "Hello World!\nTest123123\nAbrakadabra\nabc" {
|
||||
t.Error(d)
|
||||
}
|
||||
|
||||
ch.RunTextCommand(v, "mark_undo_groups_for_gluing", nil)
|
||||
ch.RunTextCommand(v, "move", Args{"forward": false, "extend": true, "by": "lines"})
|
||||
ch.RunTextCommand(v, "move", Args{"forward": false, "extend": true, "by": "lines"})
|
||||
ch.RunTextCommand(v, "move", Args{"forward": false, "extend": true, "by": "lines"})
|
||||
ch.RunTextCommand(v, "left_delete", nil)
|
||||
ch.RunTextCommand(v, "insert", Args{"characters": "a"})
|
||||
ch.RunTextCommand(v, "insert", Args{"characters": "b"})
|
||||
ch.RunTextCommand(v, "insert", Args{"characters": "c"})
|
||||
ch.RunTextCommand(v, "glue_marked_undo_groups", nil)
|
||||
if v.UndoStack().Position() != 2 {
|
||||
t.Error(v.UndoStack().Position())
|
||||
} else if d := v.Buffer().Substr(text.Region{A: 0, B: v.Buffer().Size()}); d != "Helabc" {
|
||||
t.Error(d)
|
||||
}
|
||||
|
||||
ch.RunTextCommand(v, "undo", nil)
|
||||
if v.UndoStack().Position() != 1 {
|
||||
t.Error(v.UndoStack().Position())
|
||||
} else if d := v.Buffer().Substr(text.Region{A: 0, B: v.Buffer().Size()}); d != "Hello World!\nTest123123\nAbrakadabra\nabc" {
|
||||
t.Error(d)
|
||||
}
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
. "github.com/limetext/text"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
// The IndentCommand increments indentation of selection.
|
||||
IndentCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
|
||||
// The UnindentCommand decrements indentation of selection.
|
||||
UnindentCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
)
|
||||
|
||||
func (c *IndentCommand) Run(v *View, e *Edit) error {
|
||||
indent := "\t"
|
||||
if t, ok := v.Settings().Get("translate_tabs_to_spaces", false).(bool); ok && t {
|
||||
indent = strings.Repeat(" ", getTabSize(v))
|
||||
}
|
||||
sel := v.Sel()
|
||||
|
||||
// Keep track of the indented lines
|
||||
// (go has no set collections, use that instead - struct{} doesn't take up space)
|
||||
indented_rows := map[int]struct{}{}
|
||||
|
||||
for i := 0; i < sel.Len(); i++ {
|
||||
r := sel.Get(i)
|
||||
start_row, _ := v.Buffer().RowCol(r.Begin())
|
||||
end_row, _ := v.Buffer().RowCol(r.End())
|
||||
for row := start_row; row <= end_row; row++ {
|
||||
if _, ok := indented_rows[row]; !ok {
|
||||
// Insert an indent at the beginning of the line
|
||||
pos := v.Buffer().TextPoint(row, 0)
|
||||
v.Insert(e, pos, indent)
|
||||
indented_rows[row] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *UnindentCommand) Run(v *View, e *Edit) error {
|
||||
tab_size := getTabSize(v)
|
||||
sel := v.Sel()
|
||||
unindented_rows := map[int]struct{}{}
|
||||
for i := 0; i < sel.Len(); i++ {
|
||||
r := sel.Get(i)
|
||||
start_row, _ := v.Buffer().RowCol(r.Begin())
|
||||
end_row, _ := v.Buffer().RowCol(r.End())
|
||||
for row := start_row; row <= end_row; row++ {
|
||||
if _, ok := unindented_rows[row]; !ok {
|
||||
pos := v.Buffer().TextPoint(row, 0)
|
||||
// Get the first at the beginning of the line (as many as defined by tab_size)
|
||||
sub := v.Buffer().Substr(Region{pos, pos + tab_size})
|
||||
if len(sub) == 0 {
|
||||
continue
|
||||
}
|
||||
to_remove := 0
|
||||
if sub[0] == byte('\t') {
|
||||
// Case 1: the first character is a tab, remove only it
|
||||
to_remove = 1
|
||||
} else if sub[0] == byte(' ') {
|
||||
// Case 2: the first character is a space, we remove as much spaces as we can
|
||||
to_remove = 1
|
||||
for to_remove < len(sub) && sub[to_remove] == byte(' ') {
|
||||
to_remove++
|
||||
}
|
||||
}
|
||||
if to_remove > 0 {
|
||||
v.Buffer().Erase(pos, to_remove)
|
||||
}
|
||||
unindented_rows[row] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return the tab size from the settings, defaulting to 4 if not found.
|
||||
func getTabSize(v *View) int {
|
||||
tab_size := 4
|
||||
if t, ok := v.Settings().Get("tab_size", tab_size).(int); ok {
|
||||
tab_size = t
|
||||
}
|
||||
return tab_size
|
||||
}
|
||||
|
||||
func init() {
|
||||
register([]Command{
|
||||
&IndentCommand{},
|
||||
&UnindentCommand{},
|
||||
})
|
||||
}
|
@ -1,167 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
. "github.com/limetext/text"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type indentTest struct {
|
||||
text string
|
||||
translate_tabs_to_spaces interface{}
|
||||
tab_size interface{}
|
||||
sel []Region
|
||||
expect string
|
||||
}
|
||||
|
||||
func runIndentTest(t *testing.T, tests []indentTest, command string) {
|
||||
ed := GetEditor()
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
for i, test := range tests {
|
||||
v := w.NewFile()
|
||||
defer func() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}()
|
||||
|
||||
e := v.BeginEdit()
|
||||
v.Insert(e, 0, test.text)
|
||||
v.EndEdit(e)
|
||||
|
||||
v.Sel().Clear()
|
||||
for _, r := range test.sel {
|
||||
v.Sel().Add(r)
|
||||
}
|
||||
v.Settings().Set("translate_tabs_to_spaces", test.translate_tabs_to_spaces)
|
||||
v.Settings().Set("tab_size", test.tab_size)
|
||||
|
||||
ed.CommandHandler().RunTextCommand(v, command, nil)
|
||||
if d := v.Buffer().Substr(Region{0, v.Buffer().Size()}); d != test.expect {
|
||||
t.Errorf("Test %d: Expected \n%s, but got \n%s", i, test.expect, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndent(t *testing.T) {
|
||||
tests := []indentTest{
|
||||
{ // translate_tabs_to_spaces = false
|
||||
// indent should be "\t"
|
||||
"a\n b\n c\n d\n",
|
||||
false,
|
||||
4,
|
||||
[]Region{{0, 1}},
|
||||
"\ta\n b\n c\n d\n",
|
||||
},
|
||||
{ // translate_tabs_to_spaces = nil
|
||||
// indent should be "\t"
|
||||
"a\n b\n c\n d\n",
|
||||
nil,
|
||||
1,
|
||||
[]Region{{0, 1}},
|
||||
"\ta\n b\n c\n d\n",
|
||||
},
|
||||
{ // translate_tabs_to_spaces = true and tab_size = 2
|
||||
// indent should be " "
|
||||
"a\n b\n c\n d\n",
|
||||
true,
|
||||
2,
|
||||
[]Region{{0, 1}},
|
||||
" a\n b\n c\n d\n",
|
||||
},
|
||||
{ // translate_tabs_to_spaces = true and tab_size = nil
|
||||
// indent should be " "
|
||||
"a\n b\n c\n d\n",
|
||||
true,
|
||||
nil,
|
||||
[]Region{{0, 1}},
|
||||
" a\n b\n c\n d\n",
|
||||
},
|
||||
{ // region include the 1st line and the 4th line
|
||||
// indent should add to the begining of 1st and 4th line
|
||||
"a\n b\n c\n d\n",
|
||||
false,
|
||||
1,
|
||||
[]Region{{0, 1}, {11, 12}},
|
||||
"\ta\n b\n c\n\t d\n",
|
||||
},
|
||||
{ // region selected reversely
|
||||
// should perform indent
|
||||
"a\n b\n c\n d\n",
|
||||
false,
|
||||
1,
|
||||
[]Region{{3, 0}},
|
||||
"\ta\n\t b\n c\n d\n",
|
||||
},
|
||||
}
|
||||
|
||||
runIndentTest(t, tests, "indent")
|
||||
}
|
||||
|
||||
func TestUnindent(t *testing.T) {
|
||||
tests := []indentTest{
|
||||
{ // translate_tabs_to_spaces = false
|
||||
// indent should be "\t"
|
||||
"\ta\n b\n c\n\t d\n",
|
||||
false,
|
||||
4,
|
||||
[]Region{{0, 19}},
|
||||
"a\nb\n c\n d\n",
|
||||
},
|
||||
{ // translate_tabs_to_spaces = nil
|
||||
// indent should be "\t"
|
||||
"\ta\n b\n c\n d\n",
|
||||
nil,
|
||||
1,
|
||||
[]Region{{0, 1}},
|
||||
"a\n b\n c\n d\n",
|
||||
},
|
||||
{ // translate_tabs_to_spaces = true and tab_size = 2
|
||||
// indent should be " "
|
||||
" a\n b\n c\n d\n",
|
||||
true,
|
||||
2,
|
||||
[]Region{{0, 1}},
|
||||
"a\n b\n c\n d\n",
|
||||
},
|
||||
{ // translate_tabs_to_spaces = true and tab_size = nil
|
||||
// indent should be " "
|
||||
" a\n b\n c\n d\n",
|
||||
true,
|
||||
nil,
|
||||
[]Region{{0, 1}},
|
||||
"a\n b\n c\n d\n",
|
||||
},
|
||||
{ // region include the 1st line and the 4th line
|
||||
// unindent should remove from the begining of 1st and 4th line
|
||||
"\ta\n b\n c\n \t d\n",
|
||||
false,
|
||||
1,
|
||||
[]Region{{0, 1}, {11, 12}},
|
||||
"a\n b\n c\n\t d\n",
|
||||
},
|
||||
{ // region selected reversely
|
||||
// should perform unindent
|
||||
"\ta\n\t b\n c\n d\n",
|
||||
false,
|
||||
4,
|
||||
[]Region{{3, 0}},
|
||||
"a\n b\n c\n d\n",
|
||||
},
|
||||
{ // empty strings
|
||||
// should perform unindent
|
||||
"",
|
||||
false,
|
||||
nil,
|
||||
[]Region{{0, 0}},
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
runIndentTest(t, tests, "unindent")
|
||||
}
|
@ -1,198 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
"github.com/limetext/text"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
// The InsertCommand inserts the given characters, at all
|
||||
// of the current selection locations, possibly replacing
|
||||
// text if the selection area covers one or more characters.
|
||||
InsertCommand struct {
|
||||
DefaultCommand
|
||||
// The characters to insert
|
||||
Characters string
|
||||
}
|
||||
|
||||
// The LeftDeleteCommand deletes characters to the left of the
|
||||
// current selection or the current selection if it is not empty.
|
||||
LeftDeleteCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
|
||||
// The RightDeleteCommand deletes characters to the right of the
|
||||
// current selection or the current selection if it is not empty.
|
||||
RightDeleteCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
|
||||
// The DeleteWordCommand deletes one word to right or left
|
||||
// depending on forward variable
|
||||
DeleteWordCommand struct {
|
||||
DefaultCommand
|
||||
Forward bool
|
||||
}
|
||||
)
|
||||
|
||||
func (c *InsertCommand) Run(v *View, e *Edit) error {
|
||||
sel := v.Sel()
|
||||
for i := 0; i < sel.Len(); i++ {
|
||||
r := sel.Get(i)
|
||||
if r.Size() == 0 {
|
||||
v.Insert(e, r.B, c.Characters)
|
||||
} else {
|
||||
v.Replace(e, r, c.Characters)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *LeftDeleteCommand) Run(v *View, e *Edit) error {
|
||||
trim_space := false
|
||||
tab_size := 4
|
||||
if t, ok := v.Settings().Get("translate_tabs_to_spaces", false).(bool); ok && t {
|
||||
if t, ok := v.Settings().Get("use_tab_stops", true).(bool); ok && t {
|
||||
trim_space = true
|
||||
tab_size, ok = v.Settings().Get("tab_size", 4).(int)
|
||||
if !ok {
|
||||
tab_size = 4
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sel := v.Sel()
|
||||
hasNonEmpty := sel.HasNonEmpty()
|
||||
i := 0
|
||||
for {
|
||||
l := sel.Len()
|
||||
if i >= l {
|
||||
break
|
||||
}
|
||||
r := sel.Get(i)
|
||||
if r.A == r.B && !hasNonEmpty {
|
||||
if trim_space {
|
||||
_, col := v.Buffer().RowCol(r.A)
|
||||
prev_col := r.A - (col - (col-tab_size+(tab_size-1))&^(tab_size-1))
|
||||
if prev_col < 0 {
|
||||
prev_col = 0
|
||||
}
|
||||
d := v.Buffer().SubstrR(text.Region{A: prev_col, B: r.A})
|
||||
i := len(d) - 1
|
||||
for r.A > prev_col && i >= 0 && d[i] == ' ' {
|
||||
r.A--
|
||||
i--
|
||||
}
|
||||
}
|
||||
if r.A == r.B {
|
||||
r.A--
|
||||
}
|
||||
}
|
||||
v.Erase(e, r)
|
||||
if sel.Len() != l {
|
||||
continue
|
||||
}
|
||||
i++
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RightDeleteCommand) Run(v *View, e *Edit) error {
|
||||
sel := v.Sel()
|
||||
hasNonEmpty := sel.HasNonEmpty()
|
||||
i := 0
|
||||
for {
|
||||
l := sel.Len()
|
||||
if i >= l {
|
||||
break
|
||||
}
|
||||
r := sel.Get(i)
|
||||
if r.A == r.B && !hasNonEmpty {
|
||||
r.B++
|
||||
}
|
||||
v.Erase(e, r)
|
||||
if sel.Len() != l {
|
||||
continue
|
||||
}
|
||||
i++
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *DeleteWordCommand) Run(v *View, e *Edit) error {
|
||||
var class int
|
||||
if c.Forward {
|
||||
class = CLASS_WORD_END | CLASS_PUNCTUATION_END | CLASS_LINE_START
|
||||
} else {
|
||||
class = CLASS_WORD_START | CLASS_PUNCTUATION_START | CLASS_LINE_END | CLASS_LINE_START
|
||||
}
|
||||
|
||||
sel := v.Sel()
|
||||
var rs []text.Region
|
||||
for i := 0; i < sel.Len(); i++ {
|
||||
r := sel.Get(i)
|
||||
if r.Empty() {
|
||||
p := c.findByClass(r.A, class, v)
|
||||
if c.Forward {
|
||||
r = text.Region{A: r.A, B: p}
|
||||
} else {
|
||||
r = text.Region{A: p, B: r.A}
|
||||
}
|
||||
}
|
||||
rs = append(rs, r)
|
||||
}
|
||||
sel.Clear()
|
||||
sel.AddAll(rs)
|
||||
if c.Forward {
|
||||
GetEditor().CommandHandler().RunTextCommand(v, "right_delete", nil)
|
||||
} else {
|
||||
GetEditor().CommandHandler().RunTextCommand(v, "left_delete", nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *DeleteWordCommand) findByClass(point int, class int, v *View) int {
|
||||
var end, d int
|
||||
if c.Forward {
|
||||
d = 1
|
||||
end = v.Buffer().Size()
|
||||
if point > end {
|
||||
point = end
|
||||
}
|
||||
s := v.Buffer().Substr(text.Region{A: point, B: point + 2})
|
||||
if strings.Contains(s, "\t") && strings.Contains(s, " ") {
|
||||
class = CLASS_WORD_START | CLASS_PUNCTUATION_START | CLASS_LINE_END
|
||||
}
|
||||
} else {
|
||||
d = -1
|
||||
end = 0
|
||||
if point < end {
|
||||
point = end
|
||||
}
|
||||
s := v.Buffer().Substr(text.Region{A: point - 2, B: point})
|
||||
if strings.Contains(s, "\t") && strings.Contains(s, " ") {
|
||||
class = CLASS_WORD_END | CLASS_PUNCTUATION_END | CLASS_LINE_START
|
||||
}
|
||||
}
|
||||
point += d
|
||||
for ; point != end; point += d {
|
||||
if v.Classify(point)&class != 0 {
|
||||
return point
|
||||
}
|
||||
}
|
||||
return point
|
||||
}
|
||||
|
||||
func init() {
|
||||
register([]Command{
|
||||
&InsertCommand{},
|
||||
&LeftDeleteCommand{},
|
||||
&RightDeleteCommand{},
|
||||
&DeleteWordCommand{},
|
||||
})
|
||||
}
|
@ -1,335 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
. "github.com/limetext/text"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBasic(t *testing.T) {
|
||||
data := `Hello world
|
||||
Test
|
||||
Goodbye world
|
||||
`
|
||||
ed := GetEditor()
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
v := w.NewFile()
|
||||
defer func() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}()
|
||||
|
||||
e := v.BeginEdit()
|
||||
v.Insert(e, 0, data)
|
||||
v.EndEdit(e)
|
||||
|
||||
v.Sel().Clear()
|
||||
v.Sel().Add(Region{11, 11})
|
||||
v.Sel().Add(Region{16, 16})
|
||||
v.Sel().Add(Region{30, 30})
|
||||
ed.CommandHandler().RunTextCommand(v, "left_delete", nil)
|
||||
if v.Buffer().Substr(Region{0, v.Buffer().Size()}) != `Hello worl
|
||||
Tes
|
||||
Goodbye worl
|
||||
` {
|
||||
t.Error(v.Buffer().Substr(Region{0, v.Buffer().Size()}))
|
||||
}
|
||||
ed.CommandHandler().RunTextCommand(v, "insert", Args{"characters": "a"})
|
||||
if d := v.Buffer().Substr(Region{0, v.Buffer().Size()}); d != "Hello worla\nTesa\nGoodbye worla\n" {
|
||||
lines := strings.Split(v.Buffer().Substr(Region{0, v.Buffer().Size()}), "\n")
|
||||
for _, l := range lines {
|
||||
t.Errorf("%d: '%s'", len(l), l)
|
||||
}
|
||||
}
|
||||
|
||||
v.Settings().Set("translate_tabs_to_spaces", true)
|
||||
ed.CommandHandler().RunTextCommand(v, "insert", Args{"characters": "\t"})
|
||||
if v.Buffer().Substr(Region{0, v.Buffer().Size()}) != "Hello worla \nTesa \nGoodbye worla \n" {
|
||||
lines := strings.Split(v.Buffer().Substr(Region{0, v.Buffer().Size()}), "\n")
|
||||
for _, l := range lines {
|
||||
t.Errorf("%d: '%s'", len(l), l)
|
||||
}
|
||||
}
|
||||
ed.CommandHandler().RunTextCommand(v, "left_delete", nil)
|
||||
if d := v.Buffer().Substr(Region{0, v.Buffer().Size()}); d != "Hello worla\nTesa\nGoodbye worla\n" {
|
||||
lines := strings.Split(v.Buffer().Substr(Region{0, v.Buffer().Size()}), "\n")
|
||||
for _, l := range lines {
|
||||
t.Errorf("%d: '%s'", len(l), l)
|
||||
}
|
||||
}
|
||||
|
||||
ed.CommandHandler().RunTextCommand(v, "left_delete", nil)
|
||||
if d := v.Buffer().Substr(Region{0, v.Buffer().Size()}); d != "Hello worl\nTes\nGoodbye worl\n" {
|
||||
lines := strings.Split(v.Buffer().Substr(Region{0, v.Buffer().Size()}), "\n")
|
||||
for _, l := range lines {
|
||||
t.Errorf("%d: '%s'", len(l), l)
|
||||
}
|
||||
}
|
||||
|
||||
ed.CommandHandler().RunTextCommand(v, "insert", Args{"characters": "\t"})
|
||||
if d := v.Buffer().Substr(Region{0, v.Buffer().Size()}); d != "Hello worl \nTes \nGoodbye worl \n" {
|
||||
lines := strings.Split(v.Buffer().Substr(Region{0, v.Buffer().Size()}), "\n")
|
||||
for _, l := range lines {
|
||||
t.Errorf("%d: '%s'", len(l), l)
|
||||
}
|
||||
}
|
||||
|
||||
ed.CommandHandler().RunTextCommand(v, "left_delete", nil)
|
||||
if v.Buffer().Substr(Region{0, v.Buffer().Size()}) != "Hello worl\nTes\nGoodbye worl\n" {
|
||||
lines := strings.Split(v.Buffer().Substr(Region{0, v.Buffer().Size()}), "\n")
|
||||
for _, l := range lines {
|
||||
t.Errorf("%d: '%s'", len(l), l)
|
||||
}
|
||||
}
|
||||
|
||||
v.Buffer().Erase(0, len(v.Buffer().Substr(Region{0, v.Buffer().Size()})))
|
||||
v.Buffer().Insert(0, "€þıœəßðĸʒ×ŋµåäö𝄞")
|
||||
orig := "€þıœəßðĸʒ×ŋµåäö𝄞"
|
||||
if d := v.Buffer().Substr(Region{0, v.Buffer().Size()}); d != orig {
|
||||
t.Errorf("%s\n\t%v\n\t%v", d, []byte(d), []byte(orig))
|
||||
}
|
||||
|
||||
v.Sel().Clear()
|
||||
v.Sel().Add(Region{3, 3})
|
||||
v.Sel().Add(Region{6, 6})
|
||||
v.Sel().Add(Region{9, 9})
|
||||
ed.CommandHandler().RunTextCommand(v, "left_delete", nil)
|
||||
exp := "€þœəðĸ×ŋµåäö𝄞"
|
||||
if d := v.Buffer().Substr(Region{0, v.Buffer().Size()}); d != exp {
|
||||
t.Errorf("%s\n\t%v\n\t%v", d, []byte(d), []byte(exp))
|
||||
}
|
||||
}
|
||||
|
||||
type deleteTest struct {
|
||||
in, out []Region
|
||||
text string
|
||||
ins string
|
||||
}
|
||||
|
||||
func runDeleteTest(command string, tests *[]deleteTest, t *testing.T) {
|
||||
ed := GetEditor()
|
||||
w := ed.NewWindow()
|
||||
|
||||
for i, test := range *tests {
|
||||
v := w.NewFile()
|
||||
defer func() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}()
|
||||
|
||||
e := v.BeginEdit()
|
||||
v.Insert(e, 0, test.ins)
|
||||
v.EndEdit(e)
|
||||
|
||||
v.Sel().Clear()
|
||||
for _, r := range test.in {
|
||||
v.Sel().Add(r)
|
||||
}
|
||||
var s2 RegionSet
|
||||
for _, r := range test.out {
|
||||
s2.Add(r)
|
||||
}
|
||||
|
||||
ed.CommandHandler().RunTextCommand(v, command, nil)
|
||||
if d := v.Buffer().Substr(Region{0, v.Buffer().Size()}); d != test.text {
|
||||
t.Errorf("Test %02d: Expected %s, but got %s", i, test.text, d)
|
||||
} else if !reflect.DeepEqual(*v.Sel(), s2) {
|
||||
t.Errorf("Test %02d: Expected %v, but have %v", i, s2, v.Sel())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestLeftDelete(t *testing.T) {
|
||||
tests := []deleteTest{
|
||||
{
|
||||
[]Region{{1, 1}, {2, 2}, {3, 3}, {4, 4}},
|
||||
[]Region{{0, 0}},
|
||||
"5678",
|
||||
"12345678",
|
||||
},
|
||||
{
|
||||
[]Region{{1, 1}, {3, 3}, {5, 5}, {7, 7}},
|
||||
[]Region{{0, 0}, {1, 1}, {2, 2}, {3, 3}},
|
||||
"2468",
|
||||
"12345678",
|
||||
},
|
||||
{
|
||||
[]Region{{1, 3}},
|
||||
[]Region{{1, 1}},
|
||||
"145678",
|
||||
"12345678",
|
||||
},
|
||||
{
|
||||
[]Region{{3, 1}},
|
||||
[]Region{{1, 1}},
|
||||
"145678",
|
||||
"12345678",
|
||||
},
|
||||
{
|
||||
[]Region{{100, 5}},
|
||||
[]Region{{93, 5}},
|
||||
"abc\nd",
|
||||
"abc\ndef\nghi\n",
|
||||
}, // Yes, this is indeed what ST3 does too.
|
||||
}
|
||||
|
||||
runDeleteTest("left_delete", &tests, t)
|
||||
}
|
||||
|
||||
func TestRightDelete(t *testing.T) {
|
||||
tests := []deleteTest{
|
||||
{
|
||||
[]Region{{0, 0}, {1, 1}, {2, 2}, {3, 3}},
|
||||
[]Region{{0, 0}},
|
||||
"5678",
|
||||
"12345678",
|
||||
},
|
||||
{
|
||||
[]Region{{1, 1}, {3, 3}, {5, 5}, {7, 7}},
|
||||
[]Region{{1, 1}, {2, 2}, {3, 3}, {4, 4}},
|
||||
"1357",
|
||||
"12345678",
|
||||
},
|
||||
{
|
||||
[]Region{{1, 3}},
|
||||
[]Region{{1, 1}},
|
||||
"145678",
|
||||
"12345678",
|
||||
},
|
||||
{
|
||||
[]Region{{3, 1}},
|
||||
[]Region{{1, 1}},
|
||||
"145678",
|
||||
"12345678",
|
||||
},
|
||||
}
|
||||
|
||||
runDeleteTest("right_delete", &tests, t)
|
||||
}
|
||||
|
||||
func TestInsert(t *testing.T) {
|
||||
ed := GetEditor()
|
||||
ch := ed.CommandHandler()
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
v := w.NewFile()
|
||||
defer func() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}()
|
||||
|
||||
e := v.BeginEdit()
|
||||
v.Insert(e, 0, "Hello World!\nTest123123\nAbrakadabra\n")
|
||||
v.EndEdit(e)
|
||||
|
||||
type Test struct {
|
||||
in []Region
|
||||
data string
|
||||
expd string
|
||||
expr []Region
|
||||
}
|
||||
|
||||
tests := []Test{
|
||||
{
|
||||
[]Region{{1, 1}, {3, 3}, {6, 6}},
|
||||
"a",
|
||||
"Haelalo aWorld!\nTest123123\nAbrakadabra\n",
|
||||
[]Region{{2, 2}, {5, 5}, {9, 9}},
|
||||
},
|
||||
{
|
||||
[]Region{{1, 1}, {3, 3}, {6, 9}},
|
||||
"a",
|
||||
"Haelalo ald!\nTest123123\nAbrakadabra\n",
|
||||
[]Region{{2, 2}, {5, 5}, {9, 9}},
|
||||
},
|
||||
{
|
||||
[]Region{{1, 1}, {3, 3}, {6, 9}},
|
||||
"€þıœəßðĸʒ×ŋµåäö𝄞",
|
||||
"H€þıœəßðĸʒ×ŋµåäö𝄞el€þıœəßðĸʒ×ŋµåäö𝄞lo €þıœəßðĸʒ×ŋµåäö𝄞ld!\nTest123123\nAbrakadabra\n",
|
||||
[]Region{{17, 17}, {35, 35}, {54, 54}},
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
v.Sel().Clear()
|
||||
for _, r := range test.in {
|
||||
v.Sel().Add(r)
|
||||
}
|
||||
ed.CommandHandler().RunTextCommand(v, "insert", Args{"characters": test.data})
|
||||
if d := v.Buffer().Substr(Region{0, v.Buffer().Size()}); d != test.expd {
|
||||
t.Errorf("Insert test %d failed: %s", i, d)
|
||||
}
|
||||
if sr := v.Sel().Regions(); !reflect.DeepEqual(sr, test.expr) {
|
||||
t.Errorf("Insert test %d failed: %v", i, sr)
|
||||
}
|
||||
ch.RunTextCommand(v, "undo", nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteWord(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
sel []Region
|
||||
forward bool
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
"word",
|
||||
[]Region{{4, 4}},
|
||||
false,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"'(}[word",
|
||||
[]Region{{7, 7}, {4, 4}},
|
||||
false,
|
||||
"d",
|
||||
},
|
||||
{
|
||||
"testing forwar|d\ndelete word",
|
||||
[]Region{{0, 2}, {11, 11}, {16, 16}},
|
||||
true,
|
||||
"sting for|ddelete word",
|
||||
},
|
||||
{
|
||||
"simple test on outside",
|
||||
[]Region{{-1, -1}, {6, 6}, {13, 13}, {54, 33}, {31, 31}},
|
||||
true,
|
||||
"simpletest outside",
|
||||
},
|
||||
}
|
||||
|
||||
ed := GetEditor()
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
for i, test := range tests {
|
||||
v := w.NewFile()
|
||||
defer func() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}()
|
||||
|
||||
e := v.BeginEdit()
|
||||
v.Insert(e, 0, test.text)
|
||||
v.EndEdit(e)
|
||||
|
||||
v.Sel().Clear()
|
||||
v.Sel().AddAll(test.sel)
|
||||
|
||||
ed.CommandHandler().RunTextCommand(v, "delete_word", Args{"forward": test.forward})
|
||||
if d := v.Buffer().Substr(Region{0, v.Buffer().Size()}); d != test.expect {
|
||||
t.Errorf("Test %d:\nExcepted: '%s' but got: '%s'", i, test.expect, d)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,176 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
. "github.com/limetext/text"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
// JoinCommand removes every new line in the
|
||||
// selections and the first new line after
|
||||
JoinCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
|
||||
SelectLinesCommand struct {
|
||||
DefaultCommand
|
||||
Forward bool
|
||||
}
|
||||
|
||||
SwapLineUpCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
|
||||
SwapLineDownCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
|
||||
SplitSelectionIntoLinesCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
)
|
||||
|
||||
func (c *JoinCommand) Run(v *View, e *Edit) error {
|
||||
sel := v.Sel()
|
||||
for i := 0; i < sel.Len(); i++ {
|
||||
r := sel.Get(i)
|
||||
// Removing new line and triming in the selection
|
||||
t := v.Buffer().Substr(r)
|
||||
t = strings.Replace(t, "\r", "\n", -1)
|
||||
slice := strings.Split(t, "\n")
|
||||
t = ""
|
||||
for j, s := range slice {
|
||||
if j == 0 {
|
||||
t += s
|
||||
continue
|
||||
}
|
||||
t += " " + strings.TrimLeft(s, " \t")
|
||||
}
|
||||
v.Replace(e, r, t)
|
||||
// Removing the first new line after selection
|
||||
liner := v.Buffer().FullLine(r.End())
|
||||
line := v.Buffer().Substr(liner)
|
||||
line = strings.Replace(line, "\n", "", -1)
|
||||
line = strings.Replace(line, "\r", "", -1)
|
||||
line = strings.TrimRight(line, " \t")
|
||||
// Triming the line after
|
||||
nextline := liner.End() + 1
|
||||
nextliner := v.Buffer().FullLine(nextline)
|
||||
nline := v.Buffer().Substr(nextliner)
|
||||
if nline != "" {
|
||||
v.Replace(e, nextliner, " "+strings.TrimLeft(nline, " \t"))
|
||||
}
|
||||
v.Replace(e, liner, line)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SwapLineUpCommand) Run(v *View, e *Edit) error {
|
||||
sel := v.Sel()
|
||||
for i := 0; i < sel.Len(); i++ {
|
||||
r := sel.Get(i)
|
||||
// Expand to all lines under selection
|
||||
fline := v.Buffer().Line(r.Begin())
|
||||
lline := v.Buffer().Line(r.End())
|
||||
r = Region{fline.Begin(), lline.End()}
|
||||
t := v.Buffer().Substr(r)
|
||||
// Select line before region
|
||||
bline := v.Buffer().Line(r.Begin() - 1)
|
||||
bt := v.Buffer().Substr(bline)
|
||||
v.Replace(e, r, bt)
|
||||
v.Replace(e, bline, t)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SwapLineDownCommand) Run(v *View, e *Edit) error {
|
||||
sel := v.Sel()
|
||||
for i := 0; i < sel.Len(); i++ {
|
||||
r := sel.Get(i)
|
||||
// Expand to all lines under selection
|
||||
fline := v.Buffer().Line(r.Begin())
|
||||
lline := v.Buffer().Line(r.End())
|
||||
r = Region{fline.Begin(), lline.End()}
|
||||
t := v.Buffer().Substr(r)
|
||||
// Select line before region
|
||||
nline := v.Buffer().Line(r.End() + 1)
|
||||
nt := v.Buffer().Substr(nline)
|
||||
v.Replace(e, nline, t)
|
||||
v.Replace(e, r, nt)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SelectLinesCommand) Run(v *View, e *Edit) error {
|
||||
var (
|
||||
rs []Region
|
||||
line, l Region
|
||||
d int
|
||||
)
|
||||
|
||||
sel := v.Sel()
|
||||
for i := 0; i < sel.Len(); i++ {
|
||||
r := sel.Get(i)
|
||||
// Get the distance of the selection to the begining of line
|
||||
if c.Forward {
|
||||
line = v.Buffer().FullLine(r.End())
|
||||
l = v.Buffer().Line(line.End() + 1)
|
||||
d = r.End() - line.Begin()
|
||||
} else {
|
||||
line = v.Buffer().FullLine(r.Begin())
|
||||
l = v.Buffer().Line(line.Begin() - 1)
|
||||
d = r.Begin() - line.Begin()
|
||||
}
|
||||
// If the next line lenght is more than the calculated distance
|
||||
// Put new region at the exact distance
|
||||
// If not put region at the end of the next|before line
|
||||
if l.Size() < d {
|
||||
rs = append(rs, Region{l.End(), l.End()})
|
||||
} else {
|
||||
rs = append(rs, Region{l.Begin() + d, l.Begin() + d})
|
||||
}
|
||||
}
|
||||
v.Sel().AddAll(rs)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SplitSelectionIntoLinesCommand) Run(v *View, e *Edit) error {
|
||||
var rs []Region
|
||||
|
||||
sel := v.Sel()
|
||||
for i := 0; i < sel.Len(); i++ {
|
||||
r := sel.Get(i)
|
||||
lines := v.Buffer().Lines(r)
|
||||
for i := 0; i < len(lines); i++ {
|
||||
if i != 0 {
|
||||
// Remove line endings
|
||||
r2 := v.Buffer().FullLine(lines[i-1].End())
|
||||
lines[i] = lines[i].Clip(r2)
|
||||
}
|
||||
rs = append(rs, lines[i].Intersection(r))
|
||||
}
|
||||
}
|
||||
v.Sel().Clear()
|
||||
v.Sel().AddAll(rs)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
register([]Command{
|
||||
&JoinCommand{},
|
||||
&SelectLinesCommand{},
|
||||
&SwapLineUpCommand{},
|
||||
&SwapLineDownCommand{},
|
||||
&SplitSelectionIntoLinesCommand{},
|
||||
})
|
||||
}
|
@ -1,294 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
. "github.com/limetext/text"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestJoin(t *testing.T) {
|
||||
type JoinTest struct {
|
||||
text string
|
||||
sel []Region
|
||||
expect string
|
||||
}
|
||||
|
||||
tests := []JoinTest{
|
||||
{
|
||||
"a\n\t bc",
|
||||
[]Region{{1, 1}},
|
||||
"a bc",
|
||||
},
|
||||
{
|
||||
"abc\r\n\tde",
|
||||
[]Region{{0, 0}},
|
||||
"abc de",
|
||||
},
|
||||
{
|
||||
"testing \t\t\n join",
|
||||
[]Region{{9, 8}},
|
||||
"testing join",
|
||||
},
|
||||
{
|
||||
"test\n join\n command\n whith\n multiple\n regions",
|
||||
[]Region{{2, 17}, {34, 40}},
|
||||
"test join command whith\n multiple regions",
|
||||
},
|
||||
}
|
||||
|
||||
ed := GetEditor()
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
for i, test := range tests {
|
||||
v := w.NewFile()
|
||||
defer func() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}()
|
||||
|
||||
e := v.BeginEdit()
|
||||
v.Insert(e, 0, test.text)
|
||||
v.EndEdit(e)
|
||||
|
||||
v.Sel().Clear()
|
||||
v.Sel().AddAll(test.sel)
|
||||
|
||||
ed.CommandHandler().RunTextCommand(v, "join", nil)
|
||||
if d := v.Buffer().Substr(Region{0, v.Buffer().Size()}); d != test.expect {
|
||||
t.Errorf("Test %d:\nExcepted: '%s'\nbut got: '%s'", i, test.expect, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectLines(t *testing.T) {
|
||||
type SelectLinesTest struct {
|
||||
text string
|
||||
sel []Region
|
||||
forward bool
|
||||
expect []Region
|
||||
}
|
||||
|
||||
tests := []SelectLinesTest{
|
||||
{
|
||||
"abc\ndefg",
|
||||
[]Region{{1, 1}},
|
||||
true,
|
||||
[]Region{{1, 1}, {5, 5}},
|
||||
},
|
||||
{
|
||||
"abcde\nfg",
|
||||
[]Region{{4, 4}},
|
||||
true,
|
||||
[]Region{{4, 4}, {8, 8}},
|
||||
},
|
||||
{
|
||||
"Testing select lines command\nin\nlime text",
|
||||
[]Region{{8, 14}, {30, 30}},
|
||||
true,
|
||||
[]Region{{8, 14}, {30, 30}, {31, 31}, {33, 33}},
|
||||
},
|
||||
{
|
||||
"abc\n\ndefg",
|
||||
[]Region{{6, 6}},
|
||||
false,
|
||||
[]Region{{6, 6}, {4, 4}},
|
||||
},
|
||||
{
|
||||
"Testing select lines command\nin\nlime text",
|
||||
[]Region{{30, 36}, {29, 29}},
|
||||
false,
|
||||
[]Region{{30, 36}, {29, 29}, {0, 0}, {1, 1}},
|
||||
},
|
||||
}
|
||||
|
||||
ed := GetEditor()
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
for i, test := range tests {
|
||||
v := w.NewFile()
|
||||
defer func() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}()
|
||||
|
||||
e := v.BeginEdit()
|
||||
v.Insert(e, 0, test.text)
|
||||
v.EndEdit(e)
|
||||
|
||||
v.Sel().Clear()
|
||||
v.Sel().AddAll(test.sel)
|
||||
|
||||
ed.CommandHandler().RunTextCommand(v, "select_lines", Args{"forward": test.forward})
|
||||
// Comparing regions
|
||||
d := v.Sel()
|
||||
if d.Len() != len(test.expect) {
|
||||
t.Errorf("Test %d: Excepted '%d' regions, but got '%d' regions", i, len(test.expect), d.Len())
|
||||
t.Errorf("%+v, %+v", test.expect, d.Regions())
|
||||
} else {
|
||||
var found bool
|
||||
for _, r := range test.expect {
|
||||
found = false
|
||||
for _, r2 := range d.Regions() {
|
||||
if r2 == r {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Test %d:\nRegion %+v not found in view regions: %+v", i, r, d.Regions())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwapLine(t *testing.T) {
|
||||
type SwapLineTest struct {
|
||||
text string
|
||||
sel []Region
|
||||
expect string
|
||||
}
|
||||
|
||||
uptests := []SwapLineTest{
|
||||
{
|
||||
"a\nb",
|
||||
[]Region{{2, 2}},
|
||||
"b\na",
|
||||
},
|
||||
{
|
||||
"Testing swap line up\ncommand whit multiple\nregions selected\nTesting swap line up\ncommand whit multiple\nregions selected",
|
||||
[]Region{{25, 53}, {86, 95}},
|
||||
"command whit multiple\nregions selected\nTesting swap line up\ncommand whit multiple\nTesting swap line up\nregions selected",
|
||||
},
|
||||
}
|
||||
|
||||
dwtests := []SwapLineTest{
|
||||
{
|
||||
"a\nb",
|
||||
[]Region{{1, 1}},
|
||||
"b\na",
|
||||
},
|
||||
{
|
||||
"Testing swap line up\ncommand whit multiple\nregions selected\nTesting swap line up\ncommand whit multiple\nregions selected",
|
||||
[]Region{{25, 53}, {86, 95}},
|
||||
"Testing swap line up\nTesting swap line up\ncommand whit multiple\nregions selected\nregions selected\ncommand whit multiple",
|
||||
},
|
||||
}
|
||||
|
||||
ed := GetEditor()
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
for i, test := range uptests {
|
||||
v := w.NewFile()
|
||||
defer func() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}()
|
||||
|
||||
e := v.BeginEdit()
|
||||
v.Insert(e, 0, test.text)
|
||||
v.EndEdit(e)
|
||||
|
||||
v.Sel().Clear()
|
||||
v.Sel().AddAll(test.sel)
|
||||
|
||||
ed.CommandHandler().RunTextCommand(v, "swap_line_up", nil)
|
||||
if d := v.Buffer().Substr(Region{0, v.Buffer().Size()}); d != test.expect {
|
||||
t.Errorf("Test %d:\nExcepted: '%s'\nbut got: '%s'", i, test.expect, d)
|
||||
}
|
||||
}
|
||||
|
||||
for i, test := range dwtests {
|
||||
v := w.NewFile()
|
||||
defer func() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}()
|
||||
|
||||
e := v.BeginEdit()
|
||||
v.Insert(e, 0, test.text)
|
||||
v.EndEdit(e)
|
||||
|
||||
v.Sel().Clear()
|
||||
v.Sel().AddAll(test.sel)
|
||||
|
||||
ed.CommandHandler().RunTextCommand(v, "swap_line_down", nil)
|
||||
if d := v.Buffer().Substr(Region{0, v.Buffer().Size()}); d != test.expect {
|
||||
t.Errorf("Test %d:\nExcepted: '%s'\nbut got: '%s'", i, test.expect, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitToLines(t *testing.T) {
|
||||
type SplitToLinesTest struct {
|
||||
text string
|
||||
sel []Region
|
||||
expect []Region
|
||||
}
|
||||
|
||||
tests := []SplitToLinesTest{
|
||||
{
|
||||
"ab\ncd\nef",
|
||||
[]Region{{4, 7}},
|
||||
[]Region{{4, 5}, {6, 7}},
|
||||
},
|
||||
{
|
||||
"ab\ncd\nef",
|
||||
[]Region{{0, 8}},
|
||||
[]Region{{0, 2}, {3, 5}, {6, 8}},
|
||||
},
|
||||
{
|
||||
"ab\ncd\nef",
|
||||
[]Region{{0, 4}, {4, 7}},
|
||||
[]Region{{0, 2}, {3, 4}, {4, 5}, {6, 7}},
|
||||
},
|
||||
}
|
||||
|
||||
ed := GetEditor()
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
for i, test := range tests {
|
||||
v := w.NewFile()
|
||||
defer func() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}()
|
||||
|
||||
e := v.BeginEdit()
|
||||
v.Insert(e, 0, test.text)
|
||||
v.EndEdit(e)
|
||||
|
||||
v.Sel().Clear()
|
||||
v.Sel().AddAll(test.sel)
|
||||
|
||||
ed.CommandHandler().RunTextCommand(v, "split_selection_into_lines", nil)
|
||||
// Comparing regions
|
||||
d := v.Sel()
|
||||
if d.Len() != len(test.expect) {
|
||||
t.Errorf("Test %d: Excepted '%d' regions, but got '%d' regions", i, len(test.expect), d.Len())
|
||||
t.Errorf("%+v, %+v", test.expect, d.Regions())
|
||||
} else {
|
||||
var found bool
|
||||
for _, r := range test.expect {
|
||||
found = false
|
||||
for _, r2 := range d.Regions() {
|
||||
if r2 == r {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Test %d:\nRegion %+v not found in view regions: %+v", i, r, d.Regions())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,384 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "github.com/limetext/lime/backend"
|
||||
"github.com/limetext/lime/backend/util"
|
||||
"github.com/limetext/text"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// Beginning of line
|
||||
BOL MoveToType = iota
|
||||
// End of line
|
||||
EOL
|
||||
// Beginning of file
|
||||
BOF
|
||||
// End of file
|
||||
EOF
|
||||
// Current level close bracket
|
||||
Brackets
|
||||
)
|
||||
|
||||
const (
|
||||
// Move by Characters
|
||||
Characters MoveByType = iota
|
||||
// Move by Stops (TODO(.): what exactly is a stop?)
|
||||
Stops
|
||||
// Move by Lines
|
||||
Lines
|
||||
// Move by Words
|
||||
Words
|
||||
// Move by Word Ends
|
||||
WordEnds
|
||||
// Move by Sub Words
|
||||
SubWords
|
||||
// Move by Sub Word Ends
|
||||
SubWordEnds
|
||||
// Move by Page
|
||||
Pages
|
||||
)
|
||||
|
||||
type (
|
||||
// The MoveCommand moves the current selection
|
||||
MoveCommand struct {
|
||||
DefaultCommand
|
||||
// Specifies the type of "move" operation
|
||||
By MoveByType
|
||||
// Whether the current selection should be extended or not
|
||||
Extend bool
|
||||
// Whether to move forward or backwards
|
||||
Forward bool
|
||||
// Used together with By=Stops, extends "word_separators" defined by settings
|
||||
Separators string
|
||||
// Used together with By=Stops, go to word begin
|
||||
WordBegin bool
|
||||
// Used together with By=Stops, go to word end
|
||||
WordEnd bool
|
||||
// Used together with By=Stops, go to punctuation begin
|
||||
PunctBegin bool
|
||||
// Used together with By=Stops, go to punctuation end
|
||||
PunctEnd bool
|
||||
// Used together with By=Stops, go to an empty line
|
||||
EmptyLine bool
|
||||
// Used together with By=Stops, TODO: ???
|
||||
ClipToLine bool
|
||||
}
|
||||
|
||||
// Specifies the type of "move" operation
|
||||
MoveByType int
|
||||
|
||||
// Specifies the type of "move_to" operation to perform
|
||||
MoveToType int
|
||||
|
||||
// The MoveToCommand moves or extends the current selection to the specified location
|
||||
MoveToCommand struct {
|
||||
DefaultCommand
|
||||
// The type of "move_to" operation to perform
|
||||
To MoveToType
|
||||
// Whether the current selection should be extended or not
|
||||
Extend bool
|
||||
}
|
||||
|
||||
// The ScrollLinesCommand moves the viewpoint "Amount" lines from the current location
|
||||
ScrollLinesCommand struct {
|
||||
BypassUndoCommand
|
||||
// The number of lines to scroll (positive or negative direction)
|
||||
Amount int
|
||||
}
|
||||
)
|
||||
|
||||
func move_action(v *View, extend bool, transform func(r text.Region) int) {
|
||||
sel := v.Sel()
|
||||
r := sel.Regions()
|
||||
bs := v.Buffer().Size()
|
||||
for i := range r {
|
||||
r[i].B = transform(r[i])
|
||||
if r[i].B < 0 {
|
||||
r[i].B = 0
|
||||
} else if r[i].B > bs {
|
||||
// Yes > the size, and not size-1 because the cursor being at "size"
|
||||
// is the position it will be at when we are appending
|
||||
// to the buffer
|
||||
r[i].B = bs
|
||||
}
|
||||
|
||||
if !extend {
|
||||
r[i].A = r[i].B
|
||||
}
|
||||
}
|
||||
sel.Clear()
|
||||
sel.AddAll(r)
|
||||
}
|
||||
|
||||
func (mt *MoveToType) Set(v interface{}) error {
|
||||
switch to := v.(string); to {
|
||||
case "eol":
|
||||
*mt = EOL
|
||||
case "bol":
|
||||
*mt = BOL
|
||||
case "bof":
|
||||
*mt = BOF
|
||||
case "eof":
|
||||
*mt = EOF
|
||||
case "brackets":
|
||||
*mt = Brackets
|
||||
default:
|
||||
return fmt.Errorf("move_to: Unimplemented 'to' type: %s", to)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MoveToCommand) Run(v *View, e *Edit) error {
|
||||
switch c.To {
|
||||
case EOL:
|
||||
move_action(v, c.Extend, func(r text.Region) int {
|
||||
line := v.Buffer().Line(r.B)
|
||||
return line.B
|
||||
})
|
||||
case BOL:
|
||||
move_action(v, c.Extend, func(r text.Region) int {
|
||||
line := v.Buffer().Line(r.B)
|
||||
return line.A
|
||||
})
|
||||
case BOF:
|
||||
move_action(v, c.Extend, func(r text.Region) int {
|
||||
return 0
|
||||
})
|
||||
case EOF:
|
||||
move_action(v, c.Extend, func(r text.Region) int {
|
||||
return v.Buffer().Size()
|
||||
})
|
||||
case Brackets:
|
||||
move_action(v, c.Extend, func(r text.Region) (pos int) {
|
||||
var (
|
||||
of int
|
||||
co = 1
|
||||
str, br, rv string
|
||||
opening = "([{"
|
||||
closing = ")]}"
|
||||
)
|
||||
pos = r.B
|
||||
|
||||
// next and before character
|
||||
n := v.Buffer().Substr(text.Region{r.B, r.B + 1})
|
||||
b := v.Buffer().Substr(text.Region{r.B, r.B - 1})
|
||||
if strings.ContainsAny(n, opening) {
|
||||
// TODO: Maybe it's better to use sth like view.FindByClass or even
|
||||
// view.FindByClass() function itself instead of getting whole text
|
||||
// and looping through it. With using view.FindByClass() function
|
||||
// backward we won't need to reverse the text anymore
|
||||
str = v.Buffer().Substr(text.Region{r.B + 1, v.Buffer().Size()})
|
||||
br = n
|
||||
rv = revert(n)
|
||||
of = 2
|
||||
} else if strings.ContainsAny(b, closing) {
|
||||
// TODO: same as above
|
||||
str = v.Buffer().Substr(text.Region{0, r.B - 1})
|
||||
br = b
|
||||
rv = revert(b)
|
||||
str = reverse(str)
|
||||
co = -1
|
||||
of = -2
|
||||
} else if strings.ContainsAny(n, closing) {
|
||||
// TODO: same as above
|
||||
str = v.Buffer().Substr(text.Region{0, r.B - 1})
|
||||
br = n
|
||||
rv = revert(n)
|
||||
str = reverse(str)
|
||||
co = -1
|
||||
of = -1
|
||||
} else {
|
||||
// TODO: same as above
|
||||
str = v.Buffer().Substr(text.Region{r.B, v.Buffer().Size()})
|
||||
bef := v.Buffer().Substr(text.Region{0, r.B})
|
||||
if p := strings.LastIndexAny(bef, opening); p == -1 {
|
||||
return
|
||||
} else {
|
||||
br = string(bef[p])
|
||||
rv = revert(br)
|
||||
}
|
||||
}
|
||||
count := 1
|
||||
for i, c := range str {
|
||||
if ch := string(c); ch == br {
|
||||
count++
|
||||
} else if ch == rv {
|
||||
count--
|
||||
}
|
||||
if count == 0 {
|
||||
return i*co + r.B + of
|
||||
}
|
||||
}
|
||||
return
|
||||
})
|
||||
default:
|
||||
return fmt.Errorf("move_to: Unimplemented 'to' action: %d", c.To)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MoveByType) Set(v interface{}) error {
|
||||
switch by := v.(string); by {
|
||||
case "lines":
|
||||
*m = Lines
|
||||
case "characters":
|
||||
*m = Characters
|
||||
case "stops":
|
||||
*m = Stops
|
||||
case "words":
|
||||
*m = Words
|
||||
case "word_ends":
|
||||
*m = WordEnds
|
||||
case "subwords":
|
||||
*m = SubWords
|
||||
case "subword_ends":
|
||||
*m = SubWordEnds
|
||||
case "pages":
|
||||
*m = Pages
|
||||
default:
|
||||
return fmt.Errorf("move: Unimplemented 'by' action: %s", by)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MoveCommand) Run(v *View, e *Edit) error {
|
||||
p := util.Prof.Enter("move.run.init")
|
||||
|
||||
p.Exit()
|
||||
p = util.Prof.Enter("move.run.action")
|
||||
defer p.Exit()
|
||||
|
||||
switch c.By {
|
||||
case Characters:
|
||||
dir := 1
|
||||
if !c.Forward {
|
||||
dir = -1
|
||||
}
|
||||
move_action(v, c.Extend, func(r text.Region) int {
|
||||
return r.B + dir
|
||||
})
|
||||
case Stops:
|
||||
move_action(v, c.Extend, func(in text.Region) int {
|
||||
tmp := v.Settings().Get("word_separators", DEFAULT_SEPARATORS).(string)
|
||||
defer v.Settings().Set("word_separators", tmp)
|
||||
v.Settings().Set("word_separators", c.Separators)
|
||||
|
||||
classes := 0
|
||||
if c.WordBegin {
|
||||
classes |= CLASS_WORD_START
|
||||
}
|
||||
if c.WordEnd {
|
||||
classes |= CLASS_WORD_END
|
||||
}
|
||||
if c.PunctBegin {
|
||||
classes |= CLASS_PUNCTUATION_START
|
||||
}
|
||||
if c.PunctEnd {
|
||||
classes |= CLASS_PUNCTUATION_END
|
||||
}
|
||||
if c.EmptyLine {
|
||||
classes |= CLASS_EMPTY_LINE
|
||||
}
|
||||
return v.FindByClass(in.B, c.Forward, classes)
|
||||
})
|
||||
case Lines:
|
||||
move_action(v, c.Extend, func(in text.Region) int {
|
||||
r, col := v.Buffer().RowCol(in.B)
|
||||
_ = r
|
||||
if !c.Forward {
|
||||
r--
|
||||
} else {
|
||||
r++
|
||||
}
|
||||
return v.Buffer().TextPoint(r, col)
|
||||
})
|
||||
case Words:
|
||||
move_action(v, c.Extend, func(in text.Region) int {
|
||||
return v.FindByClass(in.B, c.Forward, CLASS_WORD_START|
|
||||
CLASS_LINE_END|CLASS_LINE_START)
|
||||
})
|
||||
case WordEnds:
|
||||
move_action(v, c.Extend, func(in text.Region) int {
|
||||
return v.FindByClass(in.B, c.Forward, CLASS_WORD_END|
|
||||
CLASS_LINE_END|CLASS_LINE_START)
|
||||
})
|
||||
case SubWords:
|
||||
move_action(v, c.Extend, func(in text.Region) int {
|
||||
return v.FindByClass(in.B, c.Forward, CLASS_SUB_WORD_START|
|
||||
CLASS_WORD_START|CLASS_PUNCTUATION_START|CLASS_LINE_END|
|
||||
CLASS_LINE_START)
|
||||
})
|
||||
case SubWordEnds:
|
||||
move_action(v, c.Extend, func(in text.Region) int {
|
||||
return v.FindByClass(in.B, c.Forward, CLASS_SUB_WORD_END|
|
||||
CLASS_WORD_END|CLASS_PUNCTUATION_END|CLASS_LINE_END|
|
||||
CLASS_LINE_START)
|
||||
})
|
||||
case Pages:
|
||||
// TODO: Should know how many lines does the frontend show in one page
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MoveCommand) Default(key string) interface{} {
|
||||
if key == "separators" {
|
||||
return DEFAULT_SEPARATORS
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func revert(c string) string {
|
||||
switch c {
|
||||
case "(":
|
||||
return ")"
|
||||
case ")":
|
||||
return "("
|
||||
case "[":
|
||||
return "]"
|
||||
case "]":
|
||||
return "["
|
||||
case "{":
|
||||
return "}"
|
||||
case "}":
|
||||
return "{"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func reverse(s string) string {
|
||||
r := []rune(s)
|
||||
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
|
||||
r[i], r[j] = r[j], r[i]
|
||||
}
|
||||
return string(r)
|
||||
}
|
||||
|
||||
func (c *ScrollLinesCommand) Run(v *View, e *Edit) error {
|
||||
fe := GetEditor().Frontend()
|
||||
vr := fe.VisibleRegion(v)
|
||||
var r int
|
||||
if c.Amount >= 0 {
|
||||
r, _ = v.Buffer().RowCol(vr.Begin())
|
||||
r -= c.Amount
|
||||
} else {
|
||||
r, _ = v.Buffer().RowCol(vr.End() - 1)
|
||||
r -= c.Amount
|
||||
}
|
||||
r = v.Buffer().TextPoint(r, 0)
|
||||
fe.Show(v, text.Region{A: r, B: r})
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
register([]Command{
|
||||
&MoveCommand{},
|
||||
&MoveToCommand{},
|
||||
&ScrollLinesCommand{},
|
||||
})
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,43 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
)
|
||||
|
||||
type (
|
||||
NopApplicationCommand struct {
|
||||
BypassUndoCommand
|
||||
}
|
||||
|
||||
NopWindowCommand struct {
|
||||
BypassUndoCommand
|
||||
}
|
||||
|
||||
NopTextCommand struct {
|
||||
BypassUndoCommand
|
||||
}
|
||||
)
|
||||
|
||||
func (c *NopApplicationCommand) Run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *NopWindowCommand) Run(w *Window) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *NopTextCommand) Run(v *View, e *Edit) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
registerByName([]namedCmd{
|
||||
{"nop", &NopApplicationCommand{}},
|
||||
{"nop", &NopWindowCommand{}},
|
||||
{"nop", &NopTextCommand{}},
|
||||
})
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRunApplicationCommand(t *testing.T) {
|
||||
nopApplicationCommand := NopApplicationCommand{}
|
||||
|
||||
if nopApplicationCommand.Run() != nil {
|
||||
t.Error("No op application command running returns not nil")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRunNopWindowCommand(t *testing.T) {
|
||||
nopWindowCommand := NopWindowCommand{}
|
||||
|
||||
if nopWindowCommand.Run(&Window{}) != nil {
|
||||
t.Error("No op window command running returns not nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunNopTextCommand(t *testing.T) {
|
||||
nopTextCommand := NopTextCommand{}
|
||||
|
||||
if nopTextCommand.Run(&View{}, &Edit{}) != nil {
|
||||
t.Error("No op text command running returns not nil")
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/limetext/lime/backend"
|
||||
"github.com/limetext/lime/backend/log"
|
||||
)
|
||||
|
||||
type namedCmd struct {
|
||||
name string
|
||||
cmd backend.Command
|
||||
}
|
||||
|
||||
func registerByName(cmds []namedCmd) {
|
||||
ch := backend.GetEditor().CommandHandler()
|
||||
for _, cmd := range cmds {
|
||||
if err := ch.Register(cmd.name, cmd.cmd); err != nil {
|
||||
log.Errorf("Failed to register command %s: %s", cmd.name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func register(cmds []backend.Command) {
|
||||
ch := backend.GetEditor().CommandHandler()
|
||||
for _, cmd := range cmds {
|
||||
if err := ch.RegisterWithDefault(cmd); err != nil {
|
||||
log.Errorf("Failed to register command: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "github.com/limetext/lime/backend"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type DummyApplicationCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
|
||||
func (c *DummyApplicationCommand) Run() error {
|
||||
return fmt.Errorf("Ran")
|
||||
}
|
||||
|
||||
func (c *DummyApplicationCommand) IsChecked() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func TestRegisterByName(t *testing.T) {
|
||||
ed := GetEditor()
|
||||
|
||||
name := "dummy"
|
||||
|
||||
registerByName([]namedCmd{
|
||||
{name, &DummyApplicationCommand{}},
|
||||
})
|
||||
|
||||
err := ed.CommandHandler().RunApplicationCommand(name, nil)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected %s to run, but it didn't", name)
|
||||
} else if err.Error() != "Ran" {
|
||||
t.Errorf("Expected %s to run, but it got an error: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
ed := GetEditor()
|
||||
ac := &DummyApplicationCommand{}
|
||||
|
||||
register([]Command{
|
||||
ac,
|
||||
})
|
||||
|
||||
name := DefaultName(ac)
|
||||
err := ed.CommandHandler().RunApplicationCommand(name, nil)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected %s to run, but it didn't", name)
|
||||
} else if err.Error() != "Ran" {
|
||||
t.Errorf("Expected %s to run, but it got an error: %v", name, err)
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "github.com/limetext/lime/backend"
|
||||
)
|
||||
|
||||
type (
|
||||
SaveCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
|
||||
SaveAsCommand struct {
|
||||
DefaultCommand
|
||||
Name string
|
||||
}
|
||||
|
||||
SaveAllCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
)
|
||||
|
||||
func (c *SaveCommand) Run(v *View, e *Edit) error {
|
||||
err := v.Save()
|
||||
if err != nil {
|
||||
GetEditor().Frontend().ErrorMessage(fmt.Sprintf("Failed to save %s:n%s", v.Buffer().FileName(), err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SaveAsCommand) Run(v *View, e *Edit) error {
|
||||
err := v.SaveAs(c.Name)
|
||||
if err != nil {
|
||||
GetEditor().Frontend().ErrorMessage(fmt.Sprintf("Failed to save as %s:n%s", c.Name, err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SaveAllCommand) Run(w *Window) error {
|
||||
for _, v := range w.Views() {
|
||||
if err := v.Save(); err != nil {
|
||||
GetEditor().Frontend().ErrorMessage(fmt.Sprintf("Failed to save %s:n%s", v.Buffer().FileName(), err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
register([]Command{
|
||||
&SaveCommand{},
|
||||
&SaveAsCommand{},
|
||||
&SaveAllCommand{},
|
||||
})
|
||||
}
|
@ -1,177 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testfile string = "testdata/save_test.txt"
|
||||
|
||||
func TestSave(t *testing.T) {
|
||||
hold, err := ioutil.ReadFile(testfile)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't read test file %s", testfile)
|
||||
}
|
||||
if err := ioutil.WriteFile(testfile, []byte("Before text"), 0644); err != nil {
|
||||
t.Fatalf("Couldn't write test file %s", testfile)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
text string
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
" ab\ncd",
|
||||
"Before text ab\ncd",
|
||||
},
|
||||
{
|
||||
"\n",
|
||||
"Before text\n",
|
||||
},
|
||||
}
|
||||
|
||||
ed := GetEditor()
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
for i, test := range tests {
|
||||
err := ioutil.WriteFile(testfile, []byte("Before text"), 0644)
|
||||
if err != nil {
|
||||
t.Fatal("Could not write to test file")
|
||||
}
|
||||
|
||||
v := w.OpenFile(testfile, 0)
|
||||
e := v.BeginEdit()
|
||||
v.Insert(e, v.Buffer().Size(), test.text)
|
||||
v.EndEdit(e)
|
||||
|
||||
ed.CommandHandler().RunTextCommand(v, "save", nil)
|
||||
if v.IsDirty() {
|
||||
t.Errorf("Test %d: Expected the view to be clean, but it wasn't", i)
|
||||
}
|
||||
|
||||
if data, _ := ioutil.ReadFile(testfile); test.expect != string(data) {
|
||||
t.Errorf("Test %d: Expected %s, but got %s", i, test.expect, string(data))
|
||||
}
|
||||
|
||||
v.Close()
|
||||
|
||||
if err := ioutil.WriteFile(testfile, hold, 0644); err != nil {
|
||||
t.Fatalf("Couldn't write back test file %s", testfile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveAs(t *testing.T) {
|
||||
hold, err := ioutil.ReadFile(testfile)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't read test file %s", testfile)
|
||||
}
|
||||
if err := ioutil.WriteFile(testfile, []byte(""), 0644); err != nil {
|
||||
t.Fatalf("Couldn't write test file %s", testfile)
|
||||
}
|
||||
|
||||
ed := GetEditor()
|
||||
w := ed.NewWindow()
|
||||
// defer w.Close()
|
||||
|
||||
v := w.OpenFile(testfile, 0)
|
||||
e := v.BeginEdit()
|
||||
v.Insert(e, 0, "Testing save_as command")
|
||||
v.BeginEdit()
|
||||
|
||||
name := "testdata/save_as_test.txt"
|
||||
|
||||
ed.CommandHandler().RunTextCommand(v, "save_as", Args{"name": name})
|
||||
|
||||
if v.IsDirty() {
|
||||
t.Error("Expected the view to be clean, but it wasn't")
|
||||
}
|
||||
|
||||
if _, err := os.Stat(name); os.IsNotExist(err) {
|
||||
t.Errorf("The new test file %s wasn't created", name)
|
||||
}
|
||||
if data, _ := ioutil.ReadFile(name); "Testing save_as command" != string(data) {
|
||||
t.Errorf("Expected %s, but got %s", "Testing save_as command", string(data))
|
||||
}
|
||||
|
||||
// v.Close()
|
||||
|
||||
if err := os.Remove(name); err != nil {
|
||||
t.Errorf("Couldn't remove test file %s", name)
|
||||
}
|
||||
if err := ioutil.WriteFile(testfile, hold, 0644); err != nil {
|
||||
t.Fatalf("Couldn't write back test file %s", testfile)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveAll(t *testing.T) {
|
||||
var err error
|
||||
holds := make(map[int][]byte)
|
||||
views := make(map[int]View)
|
||||
files := []struct {
|
||||
file string
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
"testdata/save_all_test.txt",
|
||||
"Testing save all 1",
|
||||
},
|
||||
{
|
||||
"testdata/save_another_all_test.txt",
|
||||
"Testing save all 2",
|
||||
},
|
||||
}
|
||||
|
||||
ed := GetEditor()
|
||||
fe := ed.Frontend()
|
||||
if dfe, ok := fe.(*DummyFrontend); ok {
|
||||
// Make it *not* reload the file
|
||||
dfe.SetDefaultAction(false)
|
||||
}
|
||||
|
||||
w := ed.NewWindow()
|
||||
// defer w.Close()
|
||||
|
||||
for i, f := range files {
|
||||
holds[i], err = ioutil.ReadFile(f.file)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Couldn't read file %s", i, f.file)
|
||||
}
|
||||
if err := ioutil.WriteFile(f.file, []byte(""), 0644); err != nil {
|
||||
t.Fatalf("Test %d: Couldn't write test file %s", i, f.file)
|
||||
}
|
||||
|
||||
v := w.OpenFile(f.file, 0)
|
||||
views[i] = *v
|
||||
|
||||
e := v.BeginEdit()
|
||||
v.Insert(e, 0, f.expect)
|
||||
v.EndEdit(e)
|
||||
}
|
||||
|
||||
if err := ed.CommandHandler().RunWindowCommand(w, "save_all", nil); err != nil {
|
||||
t.Errorf("failed to run save_all: %s", err)
|
||||
}
|
||||
|
||||
for i, f := range files {
|
||||
if data, err := ioutil.ReadFile(f.file); err != nil {
|
||||
t.Errorf("failed to read in file: %s", err)
|
||||
} else if s := string(data); s != f.expect {
|
||||
t.Errorf("Test %d: Expected to get `%s`, but got `%s`", i, f.expect, s)
|
||||
}
|
||||
}
|
||||
|
||||
for i, f := range files {
|
||||
// v := views[i]
|
||||
// v.SetScratch(true)
|
||||
// v.Close()
|
||||
ioutil.WriteFile(f.file, holds[i], 0644)
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
. "github.com/limetext/text"
|
||||
)
|
||||
|
||||
type (
|
||||
// The SingleSelectionCommand merges multiple cursors
|
||||
// into a single one.
|
||||
SingleSelectionCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
// The SelectAllCommand selects the whole buffer of the current file
|
||||
SelectAllCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
)
|
||||
|
||||
func (c *SingleSelectionCommand) Run(v *View, e *Edit) error {
|
||||
/*
|
||||
Correct behavior of SingleSelect:
|
||||
- Remove all selection regions but the first.
|
||||
*/
|
||||
|
||||
r := v.Sel().Get(0)
|
||||
v.Sel().Clear()
|
||||
v.Sel().Add(r)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SelectAllCommand) Run(v *View, e *Edit) error {
|
||||
/*
|
||||
Correct behavior of SelectAll:
|
||||
- Select a single region of (0, view.buffersize())
|
||||
*/
|
||||
|
||||
r := Region{0, v.Buffer().Size()}
|
||||
v.Sel().Clear()
|
||||
v.Sel().Add(r)
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
register([]Command{
|
||||
&SingleSelectionCommand{},
|
||||
&SelectAllCommand{},
|
||||
})
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/text"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSingleSelection(t *testing.T) {
|
||||
tests := []findTest{
|
||||
{
|
||||
"Hello World!\nTest123123\nAbrakadabra\n",
|
||||
[]Region{{1, 1}, {2, 2}, {3, 3}, {6, 6}},
|
||||
[]Region{{1, 1}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Hello World!\nTest123123\nAbrakadabra\n",
|
||||
[]Region{{2, 2}, {3, 3}, {6, 6}},
|
||||
[]Region{{2, 2}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Hello World!\nTest123123\nAbrakadabra\n",
|
||||
[]Region{{5, 5}},
|
||||
[]Region{{5, 5}},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
runFindTest(tests, t, "single_selection")
|
||||
}
|
||||
|
||||
func TestSelectAll(t *testing.T) {
|
||||
tests := []findTest{
|
||||
{
|
||||
"Hello World!\nTest123123\nAbrakadabra\n",
|
||||
[]Region{{1, 1}, {2, 2}, {3, 3}, {6, 6}},
|
||||
[]Region{{0, 36}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Hello World!\nTest123123\nAbrakadabra\n",
|
||||
[]Region{{2, 2}, {3, 3}, {6, 6}},
|
||||
[]Region{{0, 36}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Hello World!\nTest123123\nAbrakadabra\n",
|
||||
[]Region{{5, 5}},
|
||||
[]Region{{0, 36}},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
runFindTest(tests, t, "select_all")
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
)
|
||||
|
||||
type (
|
||||
// The ToggleSettingCommand toggles the value of a setting,
|
||||
// making it false when it was true or true when it was false.
|
||||
ToggleSettingCommand struct {
|
||||
BypassUndoCommand
|
||||
Setting string
|
||||
}
|
||||
|
||||
// The SetSettingCommand set the value of a setting.
|
||||
SetSettingCommand struct {
|
||||
BypassUndoCommand
|
||||
Setting string
|
||||
Value interface{}
|
||||
}
|
||||
)
|
||||
|
||||
func (c *ToggleSettingCommand) Run(v *View, e *Edit) error {
|
||||
setting := c.Setting
|
||||
prev, boolean := v.Settings().Get(setting, false).(bool)
|
||||
// if the setting was non-boolean, it is set to true, else it is toggled
|
||||
v.Settings().Set(setting, !boolean || !prev)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SetSettingCommand) Run(v *View, e *Edit) error {
|
||||
setting := c.Setting
|
||||
v.Settings().Set(setting, c.Value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
register([]Command{
|
||||
&ToggleSettingCommand{},
|
||||
&SetSettingCommand{},
|
||||
})
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestToggleSetting(t *testing.T) {
|
||||
ed := GetEditor()
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
v := w.NewFile()
|
||||
defer func() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}()
|
||||
|
||||
v.Settings().Set("duck", true)
|
||||
v.Settings().Set("witch", false)
|
||||
v.Settings().Set("favorite_color", "blue")
|
||||
|
||||
// non-existant -> true
|
||||
ed.CommandHandler().RunTextCommand(v, "toggle_setting", Args{"setting": "rabbit"})
|
||||
if val, ok := v.Settings().Get("rabbit").(bool); !ok || !val {
|
||||
t.Errorf("Toggling an non-existant setting should make it true")
|
||||
}
|
||||
|
||||
// non-bool -> true
|
||||
ed.CommandHandler().RunTextCommand(v, "toggle_setting", Args{"setting": "favorite_color"})
|
||||
if val, ok := v.Settings().Get("favorite_color").(bool); !ok || !val {
|
||||
t.Errorf("Toggling an non-bool setting should make it true")
|
||||
}
|
||||
|
||||
// bool: true -> false
|
||||
ed.CommandHandler().RunTextCommand(v, "toggle_setting", Args{"setting": "duck"})
|
||||
if val, ok := v.Settings().Get("duck").(bool); !ok || val {
|
||||
t.Errorf("Setting should be toggled from true to false")
|
||||
}
|
||||
|
||||
// bool: false -> true
|
||||
ed.CommandHandler().RunTextCommand(v, "toggle_setting", Args{"setting": "witch"})
|
||||
if val, ok := v.Settings().Get("witch").(bool); !ok || !val {
|
||||
t.Errorf("Setting should be toggled from false to true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetSetting(t *testing.T) {
|
||||
ed := GetEditor()
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
v := w.NewFile()
|
||||
defer func() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}()
|
||||
|
||||
v.Settings().Set("favorite_color", "blue")
|
||||
|
||||
exp := "red"
|
||||
ed.CommandHandler().RunTextCommand(v, "set_setting", Args{"setting": "favorite_color", "value": exp})
|
||||
val := v.Settings().Get("favorite_color")
|
||||
if s, ok := val.(string); !ok || s != exp {
|
||||
t.Errorf("Expecting setting value to be %#v, was %#v", exp, val)
|
||||
}
|
||||
}
|
@ -1,191 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
. "github.com/limetext/text"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
// The SortLinesCommand sorts all lines
|
||||
// intersecting a selection region
|
||||
SortLinesCommand struct {
|
||||
DefaultCommand
|
||||
CaseSensitive bool
|
||||
Reverse bool
|
||||
RemoveDuplicates bool
|
||||
}
|
||||
|
||||
// The SortSelectionCommand sorts contents
|
||||
// of each selection region with respect to
|
||||
// each other
|
||||
SortSelectionCommand struct {
|
||||
DefaultCommand
|
||||
CaseSensitive bool
|
||||
Reverse bool
|
||||
RemoveDuplicates bool
|
||||
}
|
||||
|
||||
// Helper type to sort Regions by theirs positions
|
||||
regionSorter []Region
|
||||
|
||||
// Helper struct to sort strings
|
||||
textSorter struct {
|
||||
texts []string
|
||||
caseSensitive bool
|
||||
reverse bool
|
||||
}
|
||||
)
|
||||
|
||||
// regionSorter implements sort.Interface
|
||||
func (s regionSorter) Len() int { return len(s) }
|
||||
func (s regionSorter) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
func (s regionSorter) Less(i, j int) bool {
|
||||
return s[i].Begin() < s[j].Begin()
|
||||
}
|
||||
|
||||
// stringSorter implements sort.Interface
|
||||
func (s textSorter) Len() int { return len(s.texts) }
|
||||
func (s textSorter) Swap(i, j int) {
|
||||
s.texts[i], s.texts[j] = s.texts[j], s.texts[i]
|
||||
}
|
||||
func (s textSorter) Less(i, j int) bool {
|
||||
textA := s.texts[i]
|
||||
textB := s.texts[j]
|
||||
|
||||
if !s.caseSensitive {
|
||||
textA = strings.ToLower(textA)
|
||||
textB = strings.ToLower(textB)
|
||||
}
|
||||
|
||||
if s.reverse {
|
||||
return textA > textB
|
||||
}
|
||||
return textA < textB
|
||||
}
|
||||
|
||||
func (c *SortLinesCommand) Run(v *View, e *Edit) error {
|
||||
sel := v.Sel()
|
||||
buf := v.Buffer()
|
||||
|
||||
// Used as a set of int
|
||||
sorted_rows := make(map[int]bool)
|
||||
|
||||
regions := []Region{}
|
||||
texts := []string{}
|
||||
for i := 0; i < sel.Len(); i++ {
|
||||
// Get regions containing each line.
|
||||
for _, r := range buf.Lines(sel.Get(i)) {
|
||||
if ok := sorted_rows[r.Begin()]; !ok {
|
||||
sorted_rows[r.Begin()] = true
|
||||
regions = append(regions, r)
|
||||
texts = append(texts, buf.Substr(r))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(textSorter{
|
||||
texts: texts,
|
||||
caseSensitive: c.CaseSensitive,
|
||||
reverse: c.Reverse,
|
||||
})
|
||||
if c.RemoveDuplicates {
|
||||
texts = removeDuplicates(c.CaseSensitive, texts)
|
||||
}
|
||||
|
||||
sort.Sort(regionSorter(regions))
|
||||
|
||||
offset := 0
|
||||
for i, r := range regions {
|
||||
r = Region{r.A + offset, r.B + offset}
|
||||
if i < len(texts) {
|
||||
v.Replace(e, r, texts[i])
|
||||
offset += len(texts[i]) - r.Size()
|
||||
} else {
|
||||
// Erase the line and its ending
|
||||
fullLine := buf.FullLineR(r)
|
||||
v.Erase(e, fullLine)
|
||||
offset -= fullLine.Size()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SortSelectionCommand) Run(v *View, e *Edit) error {
|
||||
sel := v.Sel()
|
||||
buf := v.Buffer()
|
||||
|
||||
regions := make([]Region, sel.Len())
|
||||
texts := make([]string, sel.Len())
|
||||
for i := 0; i < sel.Len(); i++ {
|
||||
regions[i] = sel.Get(i)
|
||||
texts[i] = buf.Substr(regions[i])
|
||||
}
|
||||
|
||||
sort.Sort(textSorter{
|
||||
texts: texts,
|
||||
caseSensitive: c.CaseSensitive,
|
||||
reverse: c.Reverse,
|
||||
})
|
||||
if c.RemoveDuplicates {
|
||||
texts = removeDuplicates(c.CaseSensitive, texts)
|
||||
}
|
||||
|
||||
sort.Sort(regionSorter(regions))
|
||||
|
||||
offset := 0
|
||||
for i, r := range regions {
|
||||
r = Region{r.A + offset, r.B + offset}
|
||||
if i < len(texts) {
|
||||
v.Replace(e, r, texts[i])
|
||||
offset += len(texts[i]) - r.Size()
|
||||
} else {
|
||||
v.Erase(e, r)
|
||||
offset -= r.Size()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove duplicate ones from a sorted slice of string
|
||||
func removeDuplicates(caseSensitive bool, xs []string) []string {
|
||||
var i, j int
|
||||
for j < len(xs) {
|
||||
var accept bool
|
||||
if i > 0 {
|
||||
prev := xs[i-1]
|
||||
curr := xs[j]
|
||||
if !caseSensitive {
|
||||
prev = strings.ToLower(prev)
|
||||
curr = strings.ToLower(curr)
|
||||
}
|
||||
accept = (prev != curr)
|
||||
} else {
|
||||
accept = true
|
||||
}
|
||||
if accept {
|
||||
xs[i] = xs[j]
|
||||
i++
|
||||
j++
|
||||
} else {
|
||||
j++
|
||||
}
|
||||
}
|
||||
return xs[:i]
|
||||
}
|
||||
|
||||
func init() {
|
||||
register([]Command{
|
||||
&SortLinesCommand{},
|
||||
&SortSelectionCommand{},
|
||||
})
|
||||
}
|
@ -1,196 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
. "github.com/limetext/text"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type sortTest struct {
|
||||
text string
|
||||
caseSensitive bool
|
||||
reverse bool
|
||||
removeDuplicates bool
|
||||
sel []Region
|
||||
expect string
|
||||
}
|
||||
|
||||
func runSortTest(command string, tests []sortTest, t *testing.T) {
|
||||
ed := GetEditor()
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
for i, test := range tests {
|
||||
v := w.NewFile()
|
||||
defer func() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}()
|
||||
|
||||
e := v.BeginEdit()
|
||||
v.Insert(e, 0, test.text)
|
||||
v.EndEdit(e)
|
||||
|
||||
v.Sel().Clear()
|
||||
for _, r := range test.sel {
|
||||
v.Sel().Add(r)
|
||||
}
|
||||
|
||||
args := Args{
|
||||
"case_sensitive": test.caseSensitive,
|
||||
"reverse": test.reverse,
|
||||
"remove_duplicates": test.removeDuplicates,
|
||||
}
|
||||
ed.CommandHandler().RunTextCommand(v, command, args)
|
||||
|
||||
if d := v.Buffer().Substr(Region{0, v.Buffer().Size()}); d != test.expect {
|
||||
t.Errorf("Test %d: Excepted %#v,\n but got %#v", i, test.expect, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSortLines(t *testing.T) {
|
||||
tests := []sortTest{
|
||||
{ // Case sensitive
|
||||
"B\nc\na",
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
[]Region{{0, 5}},
|
||||
"B\na\nc",
|
||||
},
|
||||
{ // Case insensitive
|
||||
"text\nSublime\nlime",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
[]Region{{0, 17}},
|
||||
"lime\nSublime\ntext",
|
||||
},
|
||||
{ // Reverse
|
||||
"b\nc\na",
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
[]Region{{0, 5}},
|
||||
"c\nb\na",
|
||||
},
|
||||
{ // Noncontinuous selection
|
||||
"b\nc\na",
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
[]Region{{0, 1}, {4, 5}},
|
||||
"a\nc\nb",
|
||||
},
|
||||
{ // Noncontinuous selection, out of order
|
||||
"b\nc\na",
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
[]Region{{4, 5}, {0, 1}},
|
||||
"a\nc\nb",
|
||||
},
|
||||
{ // Remove duplicates
|
||||
"a\nb\na",
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
[]Region{{0, 5}},
|
||||
"a\nb\n",
|
||||
},
|
||||
{ // Remove duplicates case insensitive
|
||||
"a\nb\nA",
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
[]Region{{0, 5}},
|
||||
"a\nb\n",
|
||||
},
|
||||
{ // No duplicates removal
|
||||
"c\nb\na\nc\n",
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
[]Region{{0, 8}},
|
||||
"a\nb\nc\nc\n",
|
||||
},
|
||||
}
|
||||
|
||||
runSortTest("sort_lines", tests, t)
|
||||
}
|
||||
|
||||
func TestSortSelection(t *testing.T) {
|
||||
tests := []sortTest{
|
||||
{ // Case sensitive
|
||||
"Bca",
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
[]Region{{0, 1}, {1, 2}, {2, 3}},
|
||||
"Bac",
|
||||
},
|
||||
{ // Case insensitive
|
||||
"textSublimelime",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
[]Region{{0, 4}, {4, 11}, {11, 15}},
|
||||
"limeSublimetext",
|
||||
},
|
||||
{ // Reverse
|
||||
"bca",
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
[]Region{{0, 1}, {1, 2}, {2, 3}},
|
||||
"cba",
|
||||
},
|
||||
{ // Noncontinuous selection
|
||||
"bca",
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
[]Region{{0, 1}, {2, 3}},
|
||||
"acb",
|
||||
},
|
||||
{ // Noncontinuous selection, out of order
|
||||
"bca",
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
[]Region{{2, 3}, {0, 1}},
|
||||
"acb",
|
||||
},
|
||||
{ // Remove duplicates
|
||||
"aba",
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
[]Region{{0, 1}, {1, 2}, {2, 3}},
|
||||
"ab",
|
||||
},
|
||||
{ // Remove duplicates case insensitive
|
||||
"abA",
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
[]Region{{0, 1}, {1, 2}, {2, 3}},
|
||||
"ab",
|
||||
},
|
||||
{ // No duplicates removal
|
||||
"cbac",
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
[]Region{{0, 1}, {1, 2}, {2, 3}, {3, 4}},
|
||||
"abcc",
|
||||
},
|
||||
}
|
||||
|
||||
runSortTest("sort_selection", tests, t)
|
||||
}
|
1
backend/commands/testdata/save_all_test.txt
vendored
1
backend/commands/testdata/save_all_test.txt
vendored
@ -1 +0,0 @@
|
||||
Some Text
|
@ -1 +0,0 @@
|
||||
Some More Text
|
1
backend/commands/testdata/save_test.txt
vendored
1
backend/commands/testdata/save_test.txt
vendored
@ -1 +0,0 @@
|
||||
Some Text
|
@ -1,98 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
. "github.com/limetext/text"
|
||||
)
|
||||
|
||||
type (
|
||||
// Transpose: Swap the characters on either side of the cursor,
|
||||
// then move the cursor forward one character.
|
||||
TransposeCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
)
|
||||
|
||||
func (c *TransposeCommand) Run(v *View, e *Edit) error {
|
||||
/*
|
||||
Correct behavior of Transpose:
|
||||
- ...is actually surprisingly complicated.
|
||||
- Transpose behaves differently depending on whether any non-empty
|
||||
region is selected.
|
||||
- If there are no non-empty regions, it will swap the characters on
|
||||
either side of the cursor(s), then move all cursors forward one
|
||||
character.
|
||||
- If one region is selected, do nothing.
|
||||
- If a region is selected and there is another cursor position,
|
||||
expand the cursor to a word and swap them.
|
||||
- If two regions are selected, swap them.
|
||||
- If more than two regions are selected, rotate them forward.
|
||||
- See transpose_test.go for examples.
|
||||
*/
|
||||
|
||||
rsnew := RegionSet{}
|
||||
rs := v.Sel().Regions()
|
||||
|
||||
if v.Sel().HasNonEmpty() {
|
||||
// Build a list of transpose regions based on the current selected regions
|
||||
trs := RegionSet{}
|
||||
for _, r := range rs {
|
||||
if r.Empty() {
|
||||
trs.Add(v.Buffer().Word(r.A))
|
||||
} else {
|
||||
trs.Add(r)
|
||||
}
|
||||
}
|
||||
if trs.Len() < 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
srcr := trs.Regions()[trs.Len()-1]
|
||||
stxt := v.Buffer().Substr(srcr)
|
||||
slen := srcr.Size()
|
||||
for i := 0; i < trs.Len(); i++ {
|
||||
r := trs.Regions()[i]
|
||||
dtxt := v.Buffer().Substr(r)
|
||||
dlen := r.Size()
|
||||
v.Replace(e, r, stxt)
|
||||
trs.Adjust(r.Begin()+1, slen-dlen)
|
||||
rsnew.Add(Region{r.Begin(), r.Begin() + slen})
|
||||
stxt, slen = dtxt, dlen
|
||||
}
|
||||
|
||||
} else {
|
||||
for i, r := range rs {
|
||||
if i > 0 && r.A-1 == v.Sel().Regions()[i-1].A {
|
||||
continue
|
||||
}
|
||||
rsnew.Add(Region{r.A + 1, r.B + 1})
|
||||
if r.A == 0 || r.A >= v.Buffer().Size() {
|
||||
continue
|
||||
}
|
||||
r1 := Region{r.A - 1, r.A}
|
||||
r2 := Region{r.A, r.A + 1}
|
||||
s1 := v.Buffer().Substr(r1)
|
||||
s2 := v.Buffer().Substr(r2)
|
||||
v.Replace(e, r1, s2)
|
||||
v.Replace(e, r2, s1)
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild the active selections
|
||||
v.Sel().Clear()
|
||||
for _, r := range rsnew.Regions() {
|
||||
v.Sel().Add(Region{r.A, r.B})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
register([]Command{
|
||||
&TransposeCommand{},
|
||||
})
|
||||
}
|
@ -1,132 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
. "github.com/limetext/text"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTranspose(t *testing.T) {
|
||||
ed := GetEditor()
|
||||
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
v := w.NewFile()
|
||||
defer func() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}()
|
||||
|
||||
type Test struct {
|
||||
start string
|
||||
regions []Region
|
||||
expect string
|
||||
newregions []Region
|
||||
}
|
||||
|
||||
// Test results produced using ST3, and commands like:
|
||||
// >>> rs.add_all([sublime.Region(0,0),sublime.Region(4,7),sublime.Region(9,9),sublime.Region(15,16)])
|
||||
// >>> v.run_command("transpose")
|
||||
// >>> print([a for a in v.sel()])
|
||||
tests := []Test{
|
||||
{
|
||||
// Simple test with just one cursor position
|
||||
"one",
|
||||
[]Region{{1, 1}},
|
||||
"noe",
|
||||
[]Region{{2, 2}},
|
||||
},
|
||||
{
|
||||
// Test with several cursors, including one at the beginning of
|
||||
// the buffer, which doesn't transpose, and one beyond the end.
|
||||
"one two three four",
|
||||
[]Region{{0, 0}, {2, 2}, {5, 5}, {20, 20}},
|
||||
"oen wto three four",
|
||||
[]Region{{1, 1}, {3, 3}, {6, 6}, {21, 21}},
|
||||
},
|
||||
{
|
||||
// Similar test, but with two adjacent cursors. The second one gets
|
||||
// dropped, and doesn't transpose.
|
||||
"one two three four",
|
||||
[]Region{{0, 0}, {1, 1}, {5, 5}},
|
||||
"one wto three four",
|
||||
[]Region{{1, 1}, {6, 6}},
|
||||
},
|
||||
{
|
||||
// Test with a single region. This should do nothing.
|
||||
"one two three four",
|
||||
[]Region{{6, 10}},
|
||||
"one two three four",
|
||||
[]Region{{6, 10}},
|
||||
},
|
||||
{
|
||||
// Test with two regions of different sizes
|
||||
"one two three four",
|
||||
[]Region{{4, 7}, {8, 13}},
|
||||
"one three two four",
|
||||
[]Region{{4, 9}, {10, 13}},
|
||||
},
|
||||
{
|
||||
// Test with four regions
|
||||
"one two three four",
|
||||
[]Region{{0, 3}, {4, 7}, {8, 13}, {14, 18}},
|
||||
"four one two three",
|
||||
[]Region{{0, 4}, {5, 8}, {9, 12}, {13, 18}},
|
||||
},
|
||||
{
|
||||
// Test with one region and three cursors. The newline at the end
|
||||
// of these lines is a workaround for a bug in the Buffer.Word()
|
||||
// call, which currently has problems if it finds EOF at the end
|
||||
// of the word.
|
||||
"one two three four\n",
|
||||
[]Region{{0, 0}, {4, 7}, {9, 9}, {16, 16}},
|
||||
"four one two three\n",
|
||||
[]Region{{0, 4}, {5, 8}, {9, 12}, {13, 18}},
|
||||
},
|
||||
{
|
||||
// Test with two regions and two cursors
|
||||
"one two three four",
|
||||
[]Region{{0, 0}, {4, 7}, {9, 9}, {15, 16}},
|
||||
"o one two fthreeur",
|
||||
[]Region{{0, 1}, {2, 5}, {6, 9}, {11, 16}},
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
// Load the starting text into the buffer
|
||||
e := v.BeginEdit()
|
||||
v.Erase(e, Region{0, v.Buffer().Size()})
|
||||
v.Insert(e, 0, test.start)
|
||||
v.EndEdit(e)
|
||||
|
||||
// Add the starting selections
|
||||
v.Sel().Clear()
|
||||
for _, r := range test.regions {
|
||||
v.Sel().Add(r)
|
||||
}
|
||||
|
||||
ed.CommandHandler().RunTextCommand(v, "transpose", nil)
|
||||
|
||||
b := v.Buffer().Substr(Region{0, v.Buffer().Size()})
|
||||
if b != test.expect {
|
||||
t.Errorf("Test %d: Expected %q; got %q", i, test.expect, b)
|
||||
}
|
||||
rs := v.Sel()
|
||||
if rs.Len() == 0 {
|
||||
t.Errorf("Test %d: No regions after transpose!", i)
|
||||
}
|
||||
for ir, r := range v.Sel().Regions() {
|
||||
if r != test.newregions[ir] {
|
||||
t.Logf("Expected: %s", test.newregions)
|
||||
t.Logf("Got : %s", v.Sel().Regions())
|
||||
t.Errorf("Test %d: Selected regions wrong after transpose", i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
)
|
||||
|
||||
type (
|
||||
UndoCommand struct {
|
||||
BypassUndoCommand
|
||||
hard bool
|
||||
}
|
||||
RedoCommand struct {
|
||||
BypassUndoCommand
|
||||
hard bool
|
||||
}
|
||||
)
|
||||
|
||||
func (c *UndoCommand) Run(v *View, e *Edit) error {
|
||||
v.UndoStack().Undo(c.hard)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RedoCommand) Run(v *View, e *Edit) error {
|
||||
v.UndoStack().Redo(c.hard)
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
register([]Command{
|
||||
&UndoCommand{hard: true},
|
||||
&RedoCommand{hard: true},
|
||||
})
|
||||
|
||||
registerByName([]namedCmd{
|
||||
{"soft_undo", &UndoCommand{}},
|
||||
{"soft_redo", &RedoCommand{}},
|
||||
})
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
. "github.com/limetext/text"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUndoRedoCommands(t *testing.T) {
|
||||
ed := GetEditor()
|
||||
ch := ed.CommandHandler()
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
v := w.NewFile()
|
||||
defer func() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}()
|
||||
|
||||
edit := v.BeginEdit()
|
||||
v.Insert(edit, 0, "abcd")
|
||||
v.EndEdit(edit)
|
||||
v.Sel().Clear()
|
||||
r := []Region{
|
||||
{0, 0},
|
||||
{1, 1},
|
||||
{2, 2},
|
||||
{3, 3},
|
||||
}
|
||||
for _, r2 := range r {
|
||||
v.Sel().Add(r2)
|
||||
}
|
||||
|
||||
edit = v.BeginEdit()
|
||||
for _, ins := range "1234" {
|
||||
for i := 0; i < v.Sel().Len(); i++ {
|
||||
v.Insert(edit, v.Sel().Get(i).Begin(), string(ins))
|
||||
}
|
||||
}
|
||||
v.EndEdit(edit)
|
||||
|
||||
if v.Buffer().Substr(Region{0, v.Buffer().Size()}) != "1234a1234b1234c1234d" {
|
||||
t.Error(v.Buffer().Substr(Region{0, v.Buffer().Size()}))
|
||||
}
|
||||
ch.RunTextCommand(v, "undo", nil)
|
||||
if v.Buffer().Substr(Region{0, v.Buffer().Size()}) != "abcd" {
|
||||
t.Error("expected 'abcd', but got: ", v.Buffer().Substr(Region{0, v.Buffer().Size()}))
|
||||
}
|
||||
ch.RunTextCommand(v, "redo", nil)
|
||||
if v.Buffer().Substr(Region{0, v.Buffer().Size()}) != "1234a1234b1234c1234d" {
|
||||
t.Error("expected '1234a1234b1234c1234d', but got: ", v.Buffer().Substr(Region{0, v.Buffer().Size()}))
|
||||
}
|
||||
|
||||
v.Sel().Clear()
|
||||
r = []Region{
|
||||
{0, 0},
|
||||
{5, 5},
|
||||
{10, 10},
|
||||
{15, 15},
|
||||
}
|
||||
for _, r2 := range r {
|
||||
v.Sel().Add(r2)
|
||||
}
|
||||
|
||||
edit = v.BeginEdit()
|
||||
for _, ins := range []string{"hello ", "world"} {
|
||||
for i := 0; i < v.Sel().Len(); i++ {
|
||||
v.Insert(edit, v.Sel().Get(i).Begin(), ins)
|
||||
}
|
||||
}
|
||||
v.EndEdit(edit)
|
||||
|
||||
if v.Buffer().Substr(Region{0, v.Buffer().Size()}) != "hello world1234ahello world1234bhello world1234chello world1234d" {
|
||||
t.Error(v.Buffer().Substr(Region{0, v.Buffer().Size()}))
|
||||
}
|
||||
ch.RunTextCommand(v, "undo", nil)
|
||||
|
||||
if v.Buffer().Substr(Region{0, v.Buffer().Size()}) != "1234a1234b1234c1234d" {
|
||||
t.Error("expected '1234a1234b1234c1234d', but got: ", v.Buffer().Substr(Region{0, v.Buffer().Size()}))
|
||||
}
|
||||
ch.RunTextCommand(v, "undo", nil)
|
||||
if v.Buffer().Substr(Region{0, v.Buffer().Size()}) != "abcd" {
|
||||
t.Error("expected 'abcd', but got: ", v.Buffer().Substr(Region{0, v.Buffer().Size()}))
|
||||
}
|
||||
ch.RunTextCommand(v, "undo", nil)
|
||||
if v.Buffer().Substr(Region{0, v.Buffer().Size()}) != "" {
|
||||
t.Error("expected '', but got: ", v.Buffer().Substr(Region{0, v.Buffer().Size()}))
|
||||
}
|
||||
v.UndoStack().Redo(true)
|
||||
if v.Buffer().Substr(Region{0, v.Buffer().Size()}) != "abcd" {
|
||||
t.Error("expected 'abcd', but got: ", v.Buffer().Substr(Region{0, v.Buffer().Size()}))
|
||||
}
|
||||
|
||||
v.UndoStack().Redo(true)
|
||||
if v.Buffer().Substr(Region{0, v.Buffer().Size()}) != "1234a1234b1234c1234d" {
|
||||
t.Error("expected '1234a1234b1234c1234d', but got: ", v.Buffer().Substr(Region{0, v.Buffer().Size()}))
|
||||
}
|
||||
|
||||
v.UndoStack().Redo(true)
|
||||
if v.Buffer().Substr(Region{0, v.Buffer().Size()}) != "hello world1234ahello world1234bhello world1234chello world1234d" {
|
||||
t.Error(v.Buffer().Substr(Region{0, v.Buffer().Size()}))
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
)
|
||||
|
||||
type (
|
||||
CloseViewCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
|
||||
NextViewCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
|
||||
PrevViewCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
|
||||
SetFileTypeCommand struct {
|
||||
DefaultCommand
|
||||
Syntax string
|
||||
}
|
||||
)
|
||||
|
||||
func (c *CloseViewCommand) Run(w *Window) error {
|
||||
w.ActiveView().Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *NextViewCommand) Run(w *Window) error {
|
||||
for i, v := range w.Views() {
|
||||
if v == w.ActiveView() {
|
||||
i++
|
||||
if i == len(w.Views()) {
|
||||
i = 0
|
||||
}
|
||||
w.SetActiveView(w.Views()[i])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *PrevViewCommand) Run(w *Window) error {
|
||||
for i, v := range w.Views() {
|
||||
if v == w.ActiveView() {
|
||||
if i == 0 {
|
||||
i = len(w.Views())
|
||||
}
|
||||
i--
|
||||
w.SetActiveView(w.Views()[i])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SetFileTypeCommand) Run(v *View, e *Edit) error {
|
||||
v.SetSyntaxFile(c.Syntax)
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
register([]Command{
|
||||
&CloseViewCommand{},
|
||||
&NextViewCommand{},
|
||||
&PrevViewCommand{},
|
||||
&SetFileTypeCommand{},
|
||||
})
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCloseView(t *testing.T) {
|
||||
ed := GetEditor()
|
||||
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
l := len(w.Views())
|
||||
testPath := "open_file_test.go"
|
||||
ed.CommandHandler().RunWindowCommand(w, "open_file", Args{"path": testPath})
|
||||
ed.CommandHandler().RunWindowCommand(w, "close_view", nil)
|
||||
|
||||
if len(w.Views()) != l {
|
||||
t.Errorf("Expected %d view, but got %d", l, len(w.Views()))
|
||||
}
|
||||
|
||||
for _, v := range w.Views() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextView(t *testing.T) {
|
||||
ed := GetEditor()
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
v0 := w.NewFile()
|
||||
defer func() {
|
||||
v0.SetScratch(true)
|
||||
v0.Close()
|
||||
}()
|
||||
|
||||
v1 := w.NewFile()
|
||||
defer func() {
|
||||
v1.SetScratch(true)
|
||||
v1.Close()
|
||||
}()
|
||||
|
||||
v2 := w.NewFile()
|
||||
defer func() {
|
||||
v2.SetScratch(true)
|
||||
v2.Close()
|
||||
}()
|
||||
|
||||
v3 := w.NewFile()
|
||||
defer func() {
|
||||
v3.SetScratch(true)
|
||||
v3.Close()
|
||||
}()
|
||||
|
||||
w.SetActiveView(v1)
|
||||
|
||||
ed.CommandHandler().RunWindowCommand(w, "next_view", nil)
|
||||
|
||||
av := w.ActiveView()
|
||||
if av != v2 {
|
||||
t.Error("Expected to get v2, but didn't")
|
||||
}
|
||||
|
||||
w.SetActiveView(v3)
|
||||
|
||||
ed.CommandHandler().RunWindowCommand(w, "next_view", nil)
|
||||
|
||||
av = w.ActiveView()
|
||||
if av != v0 {
|
||||
t.Error("Expected to get v0, but didn't")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrevView(t *testing.T) {
|
||||
ed := GetEditor()
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
v0 := w.NewFile()
|
||||
defer func() {
|
||||
v0.SetScratch(true)
|
||||
v0.Close()
|
||||
}()
|
||||
|
||||
v1 := w.NewFile()
|
||||
defer func() {
|
||||
v1.SetScratch(true)
|
||||
v1.Close()
|
||||
}()
|
||||
|
||||
v2 := w.NewFile()
|
||||
defer func() {
|
||||
v2.SetScratch(true)
|
||||
v2.Close()
|
||||
}()
|
||||
|
||||
v3 := w.NewFile()
|
||||
defer func() {
|
||||
v3.SetScratch(true)
|
||||
v3.Close()
|
||||
}()
|
||||
|
||||
w.SetActiveView(v2)
|
||||
|
||||
ed.CommandHandler().RunWindowCommand(w, "prev_view", nil)
|
||||
|
||||
av := w.ActiveView()
|
||||
if av != v1 {
|
||||
t.Error("Expected to get v1, but didn't")
|
||||
}
|
||||
|
||||
w.SetActiveView(v0)
|
||||
|
||||
ed.CommandHandler().RunWindowCommand(w, "prev_view", nil)
|
||||
|
||||
av = w.ActiveView()
|
||||
if av != v3 {
|
||||
t.Error("Expected to get v3, but didn't")
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
)
|
||||
|
||||
type (
|
||||
NewWindowCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
|
||||
CloseAllCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
|
||||
CloseWindowCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
|
||||
NewWindowAppCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
|
||||
CloseWindowAppCommand struct {
|
||||
DefaultCommand
|
||||
}
|
||||
)
|
||||
|
||||
func (c *NewWindowCommand) Run(w *Window) error {
|
||||
ed := GetEditor()
|
||||
ed.SetActiveWindow(ed.NewWindow())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CloseAllCommand) Run(w *Window) error {
|
||||
w.CloseAllViews()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CloseWindowCommand) Run(w *Window) error {
|
||||
ed := GetEditor()
|
||||
ed.ActiveWindow().Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *NewWindowAppCommand) Run() error {
|
||||
ed := GetEditor()
|
||||
ed.SetActiveWindow(ed.NewWindow())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CloseWindowAppCommand) Run() error {
|
||||
ed := GetEditor()
|
||||
ed.ActiveWindow().Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *NewWindowAppCommand) IsChecked() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *CloseWindowAppCommand) IsChecked() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func init() {
|
||||
register([]Command{
|
||||
&NewWindowCommand{},
|
||||
&CloseAllCommand{},
|
||||
&CloseWindowCommand{},
|
||||
})
|
||||
|
||||
registerByName([]namedCmd{
|
||||
{"new_window", &NewWindowAppCommand{}},
|
||||
{"close_window", &CloseWindowAppCommand{}},
|
||||
})
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewWindow(t *testing.T) {
|
||||
ed := GetEditor()
|
||||
l := len(ed.Windows())
|
||||
ed.CommandHandler().RunWindowCommand(ed.ActiveWindow(), "new_window", nil)
|
||||
|
||||
if len(ed.Windows()) != l+1 {
|
||||
t.Errorf("Expected %d window, but got %d", l+1, len(ed.Windows()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloseAll(t *testing.T) {
|
||||
ed := GetEditor()
|
||||
|
||||
w := ed.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
ed.CommandHandler().RunWindowCommand(w, "new_file", nil)
|
||||
ed.CommandHandler().RunWindowCommand(w, "new_file", nil)
|
||||
ed.CommandHandler().RunWindowCommand(w, "new_file", nil)
|
||||
|
||||
ed.CommandHandler().RunWindowCommand(w, "close_all", nil)
|
||||
|
||||
if len(w.Views()) != 0 {
|
||||
t.Errorf("Expected no views, but got %d", len(w.Views()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloseWindow(t *testing.T) {
|
||||
ed := GetEditor()
|
||||
w := ed.NewWindow()
|
||||
l := len(ed.Windows())
|
||||
ed.CommandHandler().RunWindowCommand(w, "close_window", nil)
|
||||
|
||||
if len(ed.Windows()) != l-1 {
|
||||
t.Errorf("Expected %d window, but got %d", l-1, len(ed.Windows()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewAppWindow(t *testing.T) {
|
||||
ed := GetEditor()
|
||||
l := len(ed.Windows())
|
||||
ed.CommandHandler().RunApplicationCommand("new_window", nil)
|
||||
|
||||
if len(ed.Windows()) != l+1 {
|
||||
t.Errorf("Expected %d window, but got %d", l+1, len(ed.Windows()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloseAppWindow(t *testing.T) {
|
||||
ed := GetEditor()
|
||||
_ = ed.NewWindow()
|
||||
l := len(ed.Windows())
|
||||
ed.CommandHandler().RunApplicationCommand("close_window", nil)
|
||||
|
||||
if len(ed.Windows()) != l-1 {
|
||||
t.Errorf("Expected %d window, but got %d", l-1, len(ed.Windows()))
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
// The backend package defines the very soul of Lime.
|
||||
//
|
||||
// Some highlevel concepts follow.
|
||||
//
|
||||
// Frontend
|
||||
//
|
||||
// Lime is designed with the goal of having a clear frontend
|
||||
// and backend separation to allow and hopefully simplify
|
||||
// the creation of multiple frontend versions.
|
||||
//
|
||||
// The two most active frontends are at the time of writing this
|
||||
// one for terminals based on termbox-go and a GUI application
|
||||
// based on Qt's QML scripting language.
|
||||
//
|
||||
// There's also a Proof of Concept html frontend.
|
||||
//
|
||||
// Editor
|
||||
//
|
||||
// The Editor singleton represents the most fundamental interface
|
||||
// that frontends use to access the backend. It keeps a list of
|
||||
// editor windows, handles input, detects file changes as well
|
||||
// as communicate back to the frontend as needed.
|
||||
//
|
||||
// Window
|
||||
//
|
||||
// At any time there can be multiple windows of the editor open.
|
||||
// Each Window can have a different layout, settings and status.
|
||||
//
|
||||
// View
|
||||
//
|
||||
// The View class defines a "view" into a specific backing buffer.
|
||||
// Multiple views can share the same backing buffer. For instance
|
||||
// viewing the same buffer in split view, or viewing the buffer
|
||||
// with one syntax definition in one view and another syntax
|
||||
// definition in the other.
|
||||
//
|
||||
// It is very closely related to the view defined in
|
||||
// the Model-view-controller paradigm, and contains settings
|
||||
// pertaining to exactly how a buffer is shown to the user.
|
||||
//
|
||||
// Command
|
||||
//
|
||||
// The command interface defines actions to be executed either
|
||||
// for the whole application, a specific window or a specific
|
||||
// view.
|
||||
//
|
||||
// Key bindings
|
||||
//
|
||||
// Key bindings define a sequence of key-presses, a Command and
|
||||
// the command's arguments to be executed upon that sequence having
|
||||
// been pressed.
|
||||
//
|
||||
// Key bindings can optionally have multiple contexts associated with it
|
||||
// which allows the exact same key sequence to have different meaning
|
||||
// depending on context.
|
||||
//
|
||||
// See http://godoc.org/github.com/limetext/lime/backend#QueryContextCallback
|
||||
// for details.
|
||||
//
|
||||
// Settings
|
||||
//
|
||||
// Many of the components have their own key-value Settings object associated with
|
||||
// it, but settings are also nested. In other words, if the settings key does not
|
||||
// exist in the current object's settings, its parent's settings object is queried
|
||||
// next which in turn will query its parent if its settings object didn't contain the
|
||||
// key neither.
|
||||
//
|
||||
package backend
|
@ -1,58 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "github.com/limetext/text"
|
||||
)
|
||||
|
||||
type (
|
||||
// The Edit object is an internal type passed as an argument
|
||||
// to a TextCommand. All text operations need to be associated
|
||||
// with a valid Edit object.
|
||||
//
|
||||
// Think of it a bit like an SQL transaction.
|
||||
Edit struct {
|
||||
invalid bool
|
||||
composite CompositeAction
|
||||
savedSel RegionSet
|
||||
savedCount int
|
||||
command string
|
||||
args Args
|
||||
v *View
|
||||
bypassUndo bool
|
||||
}
|
||||
)
|
||||
|
||||
func newEdit(v *View) *Edit {
|
||||
ret := &Edit{
|
||||
v: v,
|
||||
savedCount: v.buffer.ChangeCount(),
|
||||
}
|
||||
for _, r := range v.Sel().Regions() {
|
||||
ret.savedSel.Add(r)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Returns a string describing this Edit object. Should typically not be manually called.
|
||||
func (e *Edit) String() string {
|
||||
return fmt.Sprintf("%s: %v, %v, %v", e.command, e.args, e.bypassUndo, e.composite)
|
||||
}
|
||||
|
||||
// Applies the action of this Edit object. Should typically not be manually called.
|
||||
func (e *Edit) Apply() {
|
||||
e.composite.Apply()
|
||||
}
|
||||
|
||||
// Reverses the application of this Edit object. Should typically not be manually called.
|
||||
func (e *Edit) Undo() {
|
||||
e.composite.Undo()
|
||||
e.v.Sel().Clear()
|
||||
for _, r := range e.savedSel.Regions() {
|
||||
e.v.Sel().Add(r)
|
||||
}
|
||||
}
|
@ -1,432 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/atotto/clipboard"
|
||||
"github.com/limetext/lime/backend/keys"
|
||||
"github.com/limetext/lime/backend/log"
|
||||
"github.com/limetext/lime/backend/packages"
|
||||
. "github.com/limetext/lime/backend/util"
|
||||
"github.com/limetext/lime/backend/watch"
|
||||
. "github.com/limetext/text"
|
||||
"path"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func init() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
GetEditor()
|
||||
}
|
||||
|
||||
type (
|
||||
Editor struct {
|
||||
HasSettings
|
||||
keys.HasKeyBindings
|
||||
*watch.Watcher
|
||||
windows []*Window
|
||||
activeWindow *Window
|
||||
logInput bool
|
||||
cmdHandler commandHandler
|
||||
console *View
|
||||
frontend Frontend
|
||||
keyInput chan (keys.KeyPress)
|
||||
clipboardSetter func(string) error
|
||||
clipboardGetter func() (string, error)
|
||||
clipboard string
|
||||
defaultSettings *HasSettings
|
||||
platformSettings *HasSettings
|
||||
defaultBindings *keys.HasKeyBindings
|
||||
platformBindings *keys.HasKeyBindings
|
||||
userBindings *keys.HasKeyBindings
|
||||
}
|
||||
|
||||
// The Frontend interface defines the API
|
||||
// for functionality that is frontend specific.
|
||||
Frontend interface {
|
||||
// Probe the frontend for the currently
|
||||
// visible region of the given view.
|
||||
VisibleRegion(v *View) Region
|
||||
|
||||
// Make the frontend show the specified region of the
|
||||
// given view.
|
||||
Show(v *View, r Region)
|
||||
|
||||
// Sets the status message shown in the status bar
|
||||
StatusMessage(string)
|
||||
|
||||
// Displays an error message to the user
|
||||
ErrorMessage(string)
|
||||
|
||||
// Displays a message dialog to the user
|
||||
MessageDialog(string)
|
||||
|
||||
// Displays an ok / cancel dialog to the user.
|
||||
// "okname" if provided will be used as the text
|
||||
// instead of "Ok" for the ok button.
|
||||
// Returns true when ok was pressed, and false when
|
||||
// cancel was pressed.
|
||||
OkCancelDialog(msg string, okname string) bool
|
||||
}
|
||||
|
||||
DummyFrontend struct {
|
||||
m sync.Mutex
|
||||
// Default return value for OkCancelDialog
|
||||
defaultAction bool
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
LIME_USER_PACKAGES_PATH = path.Join("..", "..", "packages", "User")
|
||||
LIME_PACKAGES_PATH = path.Join("..", "..", "packages")
|
||||
LIME_DEFAULTS_PATH = path.Join("..", "..", "packages", "Default")
|
||||
)
|
||||
|
||||
func (h *DummyFrontend) SetDefaultAction(action bool) {
|
||||
h.m.Lock()
|
||||
defer h.m.Unlock()
|
||||
h.defaultAction = action
|
||||
}
|
||||
func (h *DummyFrontend) StatusMessage(msg string) { log.Info(msg) }
|
||||
func (h *DummyFrontend) ErrorMessage(msg string) { log.Error(msg) }
|
||||
func (h *DummyFrontend) MessageDialog(msg string) { log.Info(msg) }
|
||||
func (h *DummyFrontend) OkCancelDialog(msg string, button string) bool {
|
||||
log.Info(msg)
|
||||
h.m.Lock()
|
||||
defer h.m.Unlock()
|
||||
return h.defaultAction
|
||||
}
|
||||
func (h *DummyFrontend) Show(v *View, r Region) {}
|
||||
func (h *DummyFrontend) VisibleRegion(v *View) Region { return Region{} }
|
||||
|
||||
var (
|
||||
ed *Editor
|
||||
edl sync.Mutex
|
||||
)
|
||||
|
||||
func GetEditor() *Editor {
|
||||
edl.Lock()
|
||||
defer edl.Unlock()
|
||||
if ed == nil {
|
||||
ed = &Editor{
|
||||
cmdHandler: commandHandler{
|
||||
ApplicationCommands: make(appcmd),
|
||||
TextCommands: make(textcmd),
|
||||
WindowCommands: make(wndcmd),
|
||||
verbose: true,
|
||||
},
|
||||
frontend: &DummyFrontend{},
|
||||
console: &View{
|
||||
buffer: NewBuffer(),
|
||||
scratch: true,
|
||||
},
|
||||
keyInput: make(chan keys.KeyPress, 32),
|
||||
}
|
||||
var err error
|
||||
if ed.Watcher, err = watch.NewWatcher(); err != nil {
|
||||
log.Errorf("Couldn't create watcher: %s", err)
|
||||
}
|
||||
ed.console.Settings().Set("is_widget", true)
|
||||
ed.defaultSettings = new(HasSettings)
|
||||
ed.platformSettings = new(HasSettings)
|
||||
ed.Settings() // Just to initialize it
|
||||
ed.defaultBindings = new(keys.HasKeyBindings)
|
||||
ed.platformBindings = new(keys.HasKeyBindings)
|
||||
ed.userBindings = new(keys.HasKeyBindings)
|
||||
log.AddFilter("console", log.DEBUG, log.NewLogWriter(ed.handleLog))
|
||||
go ed.inputthread()
|
||||
go ed.Observe()
|
||||
}
|
||||
return ed
|
||||
}
|
||||
|
||||
func (e *Editor) Frontend() Frontend {
|
||||
return e.frontend
|
||||
}
|
||||
|
||||
func (e *Editor) SetFrontend(f Frontend) {
|
||||
e.frontend = f
|
||||
}
|
||||
|
||||
func setClipboard(n string) error {
|
||||
return clipboard.WriteAll(n)
|
||||
}
|
||||
|
||||
func getClipboard() (string, error) {
|
||||
return clipboard.ReadAll()
|
||||
}
|
||||
|
||||
func (e *Editor) Init() {
|
||||
log.Info("Initializing")
|
||||
e.SetClipboardFuncs(setClipboard, getClipboard)
|
||||
e.loadKeyBindings()
|
||||
e.loadSettings()
|
||||
OnInit.call()
|
||||
}
|
||||
|
||||
func (e *Editor) SetClipboardFuncs(setter func(string) error, getter func() (string, error)) {
|
||||
e.clipboardSetter = setter
|
||||
e.clipboardGetter = getter
|
||||
}
|
||||
|
||||
func (e *Editor) load(pkg *packages.Packet) {
|
||||
if err := pkg.Load(); err != nil {
|
||||
log.Errorf("Failed to load packet %s: %s", pkg.Name(), err)
|
||||
} else {
|
||||
log.Info("Loaded %s", pkg.Name())
|
||||
if err := e.Watch(pkg.Name(), pkg); err != nil {
|
||||
log.Warn("Couldn't watch %s: %s", pkg.Name(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Editor) loadKeyBindings() {
|
||||
e.KeyBindings().SetParent(e.userBindings)
|
||||
e.userBindings.KeyBindings().SetParent(e.platformBindings)
|
||||
e.platformBindings.KeyBindings().SetParent(e.defaultBindings)
|
||||
|
||||
p := path.Join(LIME_DEFAULTS_PATH, "Default.sublime-keymap")
|
||||
defPckt := packages.NewPacket(p, e.defaultBindings.KeyBindings())
|
||||
e.load(defPckt)
|
||||
|
||||
p = path.Join(LIME_DEFAULTS_PATH, "Default ("+e.Plat()+").sublime-keymap")
|
||||
platPckt := packages.NewPacket(p, e.platformBindings.KeyBindings())
|
||||
e.load(platPckt)
|
||||
|
||||
p = path.Join(LIME_USER_PACKAGES_PATH, "Default.sublime-keymap")
|
||||
usrPckt := packages.NewPacket(p, e.userBindings.KeyBindings())
|
||||
e.load(usrPckt)
|
||||
|
||||
p = path.Join(LIME_USER_PACKAGES_PATH, "Default ("+e.Plat()+").sublime-keymap")
|
||||
usrPlatPckt := packages.NewPacket(p, e.KeyBindings())
|
||||
e.load(usrPlatPckt)
|
||||
}
|
||||
|
||||
func (e *Editor) loadSettings() {
|
||||
e.platformSettings.Settings().SetParent(e.defaultSettings)
|
||||
e.Settings().SetParent(e.platformSettings)
|
||||
|
||||
p := path.Join(LIME_DEFAULTS_PATH, "Preferences.sublime-settings")
|
||||
defPckt := packages.NewPacket(p, e.defaultSettings.Settings())
|
||||
e.load(defPckt)
|
||||
|
||||
p = path.Join(LIME_DEFAULTS_PATH, "Preferences ("+e.Plat()+").sublime-settings")
|
||||
platPckt := packages.NewPacket(p, e.platformSettings.Settings())
|
||||
e.load(platPckt)
|
||||
|
||||
p = path.Join(LIME_USER_PACKAGES_PATH, "Preferences.sublime-settings")
|
||||
usrPckt := packages.NewPacket(p, e.Settings())
|
||||
e.load(usrPckt)
|
||||
}
|
||||
|
||||
func (e *Editor) PackagesPath() string {
|
||||
return LIME_PACKAGES_PATH
|
||||
}
|
||||
|
||||
func (e *Editor) Console() *View {
|
||||
return e.console
|
||||
}
|
||||
|
||||
func (e *Editor) Windows() []*Window {
|
||||
edl.Lock()
|
||||
defer edl.Unlock()
|
||||
ret := make([]*Window, len(e.windows))
|
||||
copy(ret, e.windows)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (e *Editor) SetActiveWindow(w *Window) {
|
||||
e.activeWindow = w
|
||||
}
|
||||
|
||||
func (e *Editor) ActiveWindow() *Window {
|
||||
return e.activeWindow
|
||||
}
|
||||
|
||||
func (e *Editor) NewWindow() *Window {
|
||||
edl.Lock()
|
||||
e.windows = append(e.windows, &Window{})
|
||||
w := e.windows[len(e.windows)-1]
|
||||
edl.Unlock()
|
||||
w.Settings().SetParent(e)
|
||||
e.SetActiveWindow(w)
|
||||
OnNewWindow.Call(w)
|
||||
return w
|
||||
}
|
||||
|
||||
func (e *Editor) remove(w *Window) {
|
||||
edl.Lock()
|
||||
defer edl.Unlock()
|
||||
for i, ww := range e.windows {
|
||||
if w == ww {
|
||||
end := len(e.windows) - 1
|
||||
if i != end {
|
||||
copy(e.windows[i:], e.windows[i+1:])
|
||||
}
|
||||
e.windows = e.windows[:end]
|
||||
return
|
||||
}
|
||||
}
|
||||
log.Errorf("Wanted to remove window %+v, but it doesn't appear to be a child of this editor", w)
|
||||
}
|
||||
|
||||
func (e *Editor) Arch() string {
|
||||
return runtime.GOARCH
|
||||
}
|
||||
|
||||
func (e *Editor) Platform() string {
|
||||
return runtime.GOOS
|
||||
}
|
||||
|
||||
func (e *Editor) Plat() string {
|
||||
switch e.Platform() {
|
||||
case "windows":
|
||||
return "Windows"
|
||||
case "darwin":
|
||||
return "OSX"
|
||||
}
|
||||
return "Linux"
|
||||
}
|
||||
|
||||
func (e *Editor) Version() string {
|
||||
return "0"
|
||||
}
|
||||
|
||||
func (e *Editor) CommandHandler() CommandHandler {
|
||||
return &e.cmdHandler
|
||||
}
|
||||
|
||||
func (e *Editor) HandleInput(kp keys.KeyPress) {
|
||||
e.keyInput <- kp
|
||||
}
|
||||
|
||||
func (e *Editor) inputthread() {
|
||||
pc := 0
|
||||
var lastBindings keys.KeyBindings
|
||||
doinput := func(kp keys.KeyPress) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Errorf("Panic in inputthread: %v\n%s", r, string(debug.Stack()))
|
||||
if pc > 0 {
|
||||
panic(r)
|
||||
}
|
||||
pc++
|
||||
}
|
||||
}()
|
||||
p := Prof.Enter("hi")
|
||||
defer p.Exit()
|
||||
|
||||
lvl := log.FINE
|
||||
if e.logInput {
|
||||
lvl++
|
||||
}
|
||||
log.Logf(lvl, "Key: %v", kp)
|
||||
if lastBindings.SeqIndex() == 0 {
|
||||
lastBindings = *e.KeyBindings()
|
||||
}
|
||||
try_again:
|
||||
possible_actions := lastBindings.Filter(kp)
|
||||
lastBindings = possible_actions
|
||||
|
||||
// TODO?
|
||||
var (
|
||||
wnd *Window
|
||||
v *View
|
||||
)
|
||||
if wnd = e.ActiveWindow(); wnd != nil {
|
||||
v = wnd.ActiveView()
|
||||
}
|
||||
|
||||
qc := func(key string, operator Op, operand interface{}, match_all bool) bool {
|
||||
return OnQueryContext.Call(v, key, operator, operand, match_all) == True
|
||||
}
|
||||
|
||||
if action := possible_actions.Action(qc); action != nil {
|
||||
p2 := Prof.Enter("hi.perform")
|
||||
e.RunCommand(action.Command, action.Args)
|
||||
p2.Exit()
|
||||
} else if possible_actions.SeqIndex() > 1 {
|
||||
// TODO: this disables having keyBindings with more than 2 key sequence
|
||||
lastBindings = *e.KeyBindings()
|
||||
goto try_again
|
||||
} else if kp.IsCharacter() {
|
||||
p2 := Prof.Enter("hi.character")
|
||||
log.Finest("[editor.inputthread] kp: |%s|, pos: %v", kp.Text, possible_actions)
|
||||
if err := e.CommandHandler().RunTextCommand(v, "insert", Args{"characters": kp.Text}); err != nil {
|
||||
log.Debug("Couldn't run textcommand: %s", err)
|
||||
}
|
||||
p2.Exit()
|
||||
}
|
||||
}
|
||||
for kp := range e.keyInput {
|
||||
doinput(kp)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Editor) LogInput(l bool) {
|
||||
e.logInput = l
|
||||
}
|
||||
|
||||
func (e *Editor) LogCommands(l bool) {
|
||||
e.cmdHandler.log = l
|
||||
}
|
||||
|
||||
func (e *Editor) RunCommand(name string, args Args) {
|
||||
// TODO?
|
||||
var (
|
||||
wnd *Window
|
||||
v *View
|
||||
)
|
||||
if wnd = e.ActiveWindow(); wnd != nil {
|
||||
v = wnd.ActiveView()
|
||||
}
|
||||
|
||||
// TODO: what's the command precedence?
|
||||
if c := e.cmdHandler.TextCommands[name]; c != nil {
|
||||
if err := e.CommandHandler().RunTextCommand(v, name, args); err != nil {
|
||||
log.Debug("Couldn't run textcommand: %s", err)
|
||||
}
|
||||
} else if c := e.cmdHandler.WindowCommands[name]; c != nil {
|
||||
if err := e.CommandHandler().RunWindowCommand(wnd, name, args); err != nil {
|
||||
log.Debug("Couldn't run windowcommand: %s", err)
|
||||
}
|
||||
} else if c := e.cmdHandler.ApplicationCommands[name]; c != nil {
|
||||
if err := e.CommandHandler().RunApplicationCommand(name, args); err != nil {
|
||||
log.Debug("Couldn't run applicationcommand: %s", err)
|
||||
}
|
||||
} else {
|
||||
log.Debug("Couldn't find command to run")
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Editor) SetClipboard(n string) {
|
||||
if err := e.clipboardSetter(n); err != nil {
|
||||
log.Errorf("Could not set clipboard: %v", err)
|
||||
}
|
||||
|
||||
// Keep a local copy in case the system clipboard isn't working
|
||||
e.clipboard = n
|
||||
}
|
||||
|
||||
func (e *Editor) GetClipboard() string {
|
||||
if n, err := e.clipboardGetter(); err == nil {
|
||||
return n
|
||||
} else {
|
||||
log.Errorf("Could not get clipboard: %v", err)
|
||||
}
|
||||
|
||||
return e.clipboard
|
||||
}
|
||||
|
||||
func (e *Editor) handleLog(s string) {
|
||||
c := e.Console()
|
||||
f := fmt.Sprintf("%08d %d %s", c.Buffer().Size(), len(s), s)
|
||||
edit := c.BeginEdit()
|
||||
c.Insert(edit, c.Buffer().Size(), f)
|
||||
c.EndEdit(edit)
|
||||
}
|
@ -1,201 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package backend
|
||||
|
||||
import (
|
||||
"github.com/limetext/lime/backend/keys"
|
||||
"github.com/limetext/lime/backend/packages"
|
||||
"path"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
LIME_PACKAGES_PATH = path.Join("..", "packages")
|
||||
LIME_USER_PACKAGES_PATH = path.Join("..", "packages", "User")
|
||||
LIME_DEFAULTS_PATH = path.Join("..", "packages", "Default")
|
||||
}
|
||||
|
||||
func TestGetEditor(t *testing.T) {
|
||||
editor := GetEditor()
|
||||
if editor == nil {
|
||||
t.Error("Expected an editor, but got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadKeyBinding(t *testing.T) {
|
||||
editor := GetEditor()
|
||||
pkg := packages.NewPacket("testdata/Default.sublime-keymap", editor.KeyBindings())
|
||||
editor.load(pkg)
|
||||
|
||||
kb := editor.KeyBindings().Filter(keys.KeyPress{Key: 'i'})
|
||||
if expectedLen := 3; kb.Len() != expectedLen {
|
||||
t.Errorf("Expected to have %d keys in the filter, but it had %d", expectedLen, kb.Len())
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadKeyBindings(t *testing.T) {
|
||||
editor := GetEditor()
|
||||
editor.loadKeyBindings()
|
||||
|
||||
if editor.defaultBindings.KeyBindings().Len() <= 0 {
|
||||
t.Errorf("Expected editor to have some keys bound, but it didn't")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSetting(t *testing.T) {
|
||||
editor := GetEditor()
|
||||
editor.load(packages.NewPacket("testdata/Default.sublime-settings", editor.Settings()))
|
||||
|
||||
if editor.Settings().Has("tab_size") != true {
|
||||
t.Error("Expected editor settings to have tab_size, but it didn't")
|
||||
}
|
||||
|
||||
tab_size := editor.Settings().Get("tab_size").(float64)
|
||||
if tab_size != 4 {
|
||||
t.Errorf("Expected tab_size to equal 4, got: %v", tab_size)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSettings(t *testing.T) {
|
||||
editor := GetEditor()
|
||||
editor.loadSettings()
|
||||
|
||||
if editor.Settings().Has("tab_size") != true {
|
||||
t.Error("Expected editor settings to have tab_size, but it didn't")
|
||||
}
|
||||
|
||||
plat := editor.Settings().Parent()
|
||||
switch editor.Platform() {
|
||||
case "windows":
|
||||
if plat.Settings().Get("font_face", "") != "Consolas" {
|
||||
t.Errorf("Expected windows font_face be Consolas, but is %s", plat.Settings().Get("font_face", ""))
|
||||
}
|
||||
case "darwin":
|
||||
if plat.Settings().Get("font_face", "") != "Menlo" {
|
||||
t.Errorf("Expected OSX font_face be Menlo, but is %s", plat.Settings().Get("font_face", ""))
|
||||
}
|
||||
default:
|
||||
if plat.Settings().Get("font_face", "") != "Monospace" {
|
||||
t.Errorf("Expected Linux font_face be Monospace, but is %s", plat.Settings().Get("font_face", ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
editor := GetEditor()
|
||||
editor.Init()
|
||||
|
||||
if editor.defaultBindings.KeyBindings().Len() <= 0 {
|
||||
t.Errorf("Expected editor to have some keys bound, but it didn't")
|
||||
}
|
||||
|
||||
if editor.Settings().Parent().Settings().Parent().Settings().Has("tab_size") != true {
|
||||
t.Error("Expected editor settings to have tab_size, but it didn't")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewWindow(t *testing.T) {
|
||||
editor := GetEditor()
|
||||
l := len(editor.Windows())
|
||||
|
||||
w := editor.NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
if len(editor.Windows()) != l+1 {
|
||||
t.Errorf("Expected 1 window, but got %d", len(editor.Windows()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveWindow(t *testing.T) {
|
||||
editor := GetEditor()
|
||||
l := len(editor.Windows())
|
||||
|
||||
w0 := editor.NewWindow()
|
||||
defer w0.Close()
|
||||
|
||||
editor.remove(w0)
|
||||
|
||||
if len(editor.Windows()) != l {
|
||||
t.Errorf("Expected the window to be removed, but %d still remain", len(editor.Windows()))
|
||||
}
|
||||
|
||||
w1 := editor.NewWindow()
|
||||
defer w1.Close()
|
||||
|
||||
w2 := editor.NewWindow()
|
||||
defer w2.Close()
|
||||
|
||||
editor.remove(w1)
|
||||
|
||||
if len(editor.Windows()) != l+1 {
|
||||
t.Errorf("Expected the window to be removed, but %d still remain", len(editor.Windows()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetActiveWindow(t *testing.T) {
|
||||
editor := GetEditor()
|
||||
|
||||
w1 := editor.NewWindow()
|
||||
defer w1.Close()
|
||||
|
||||
w2 := editor.NewWindow()
|
||||
defer w2.Close()
|
||||
|
||||
if editor.ActiveWindow() != w2 {
|
||||
t.Error("Expected the newest window to be active, but it wasn't")
|
||||
}
|
||||
|
||||
editor.SetActiveWindow(w1)
|
||||
|
||||
if editor.ActiveWindow() != w1 {
|
||||
t.Error("Expected the first window to be active, but it wasn't")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetFrontend(t *testing.T) {
|
||||
f := DummyFrontend{}
|
||||
|
||||
editor := GetEditor()
|
||||
editor.SetFrontend(&f)
|
||||
|
||||
if editor.Frontend() != &f {
|
||||
t.Errorf("Expected a DummyFrontend to be set, but got %T", editor.Frontend())
|
||||
}
|
||||
}
|
||||
|
||||
func TestClipboard(t *testing.T) {
|
||||
editor := GetEditor()
|
||||
|
||||
// Put back whatever was already there.
|
||||
clip := editor.GetClipboard()
|
||||
defer editor.SetClipboard(clip)
|
||||
|
||||
s := "test0"
|
||||
|
||||
editor.SetClipboard(s)
|
||||
|
||||
if editor.GetClipboard() != s {
|
||||
t.Errorf("Expected %q to be on the clipboard, but got %q", s, editor.GetClipboard())
|
||||
}
|
||||
|
||||
s = "test1"
|
||||
|
||||
editor.SetClipboard(s)
|
||||
|
||||
if editor.GetClipboard() != s {
|
||||
t.Errorf("Expected %q to be on the clipboard, but got %q", s, editor.GetClipboard())
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleInput(t *testing.T) {
|
||||
editor := GetEditor()
|
||||
kp := keys.KeyPress{Key: 'i'}
|
||||
|
||||
editor.HandleInput(kp)
|
||||
|
||||
if ki := <-editor.keyInput; ki != kp {
|
||||
t.Errorf("Expected %s to be on the input buffer, but got %s", kp, ki)
|
||||
}
|
||||
}
|
@ -1,191 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package backend
|
||||
|
||||
import (
|
||||
"github.com/limetext/lime/backend/log"
|
||||
"github.com/limetext/lime/backend/util"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
// An event callback dealing with View events.
|
||||
ViewEventCallback func(v *View)
|
||||
// A ViewEvent is simply a bunch of ViewEventCallbacks.
|
||||
ViewEvent []ViewEventCallback
|
||||
|
||||
// The return value returned from a QueryContextCallback.
|
||||
QueryContextReturn int
|
||||
|
||||
// The context is queried when trying to figure out what action should be performed when
|
||||
// certain conditions are met.
|
||||
//
|
||||
// Context is just a string identifier, an optional comparison operator, an optional operand, and an optional
|
||||
// match_all boolean. The data of the context is optionally provided together with a key binding and the key's
|
||||
// action will only be considered if the context conditions are met.
|
||||
//
|
||||
// Exactly how these values are interpreted is up to the individual context handlers, which may be fully
|
||||
// customized by implementing the callback in a plugin.
|
||||
//
|
||||
// For instance pressing the key 'j' will have a different meaning when in a VI command mode emulation
|
||||
// and when in a VI insert mode emulation. A plugin would then define two key binding entries for 'j',
|
||||
// describe the key binding context to be able to discern which action is appropriate when 'j' is then pressed.
|
||||
QueryContextCallback func(v *View, key string, operator util.Op, operand interface{}, match_all bool) QueryContextReturn
|
||||
|
||||
// A QueryContextEvent is simply a bunch of QueryContextCallbacks.
|
||||
QueryContextEvent []QueryContextCallback
|
||||
|
||||
// A WindowEventCallback deals with Window events.
|
||||
WindowEventCallback func(w *Window)
|
||||
// A WindowEvent is simply a bunch of WindowEventCallbacks.
|
||||
WindowEvent []WindowEventCallback
|
||||
|
||||
// The InitCallback allows complex (i.e. time consuming)
|
||||
// initiation code to be deferred until after the UI is up and running.
|
||||
InitCallback func()
|
||||
|
||||
// The InitEvent is executed once at startup, after the UI is up and running and
|
||||
// is typically used by feature modules to defer heavy initialization work
|
||||
// such as scanning for plugins, loading key bindings, macros etc.
|
||||
InitEvent []InitCallback
|
||||
)
|
||||
|
||||
const (
|
||||
True QueryContextReturn = iota //< Returned when the context query matches.
|
||||
False //< Returned when the context query does not match.
|
||||
Unknown //< Returned when the QueryContextCallback does not know how to deal with the given context.
|
||||
)
|
||||
|
||||
// Add the InitCallback to the InitEvent to be called during initialization.
|
||||
// This should be called in a module's init() function.
|
||||
func (ie *InitEvent) Add(i InitCallback) {
|
||||
*ie = append(*ie, i)
|
||||
}
|
||||
|
||||
// Execute the InitEvent.
|
||||
func (ie *InitEvent) call() {
|
||||
log.Debug("OnInit callbacks executing")
|
||||
defer log.Debug("OnInit callbacks finished")
|
||||
for _, ev := range *ie {
|
||||
ev()
|
||||
}
|
||||
}
|
||||
|
||||
// Add the provided ViewEventCallback to this ViewEvent
|
||||
// TODO(.): Support removing ViewEventCallbacks?
|
||||
func (ve *ViewEvent) Add(cb ViewEventCallback) {
|
||||
*ve = append(*ve, cb)
|
||||
}
|
||||
|
||||
// Trigger this ViewEvent by calling all the registered callbacks in order of registration.
|
||||
func (ve *ViewEvent) Call(v *View) {
|
||||
log.Finest("%s(%v)", evNames[ve], v.Id())
|
||||
for _, ev := range *ve {
|
||||
ev(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Add the provided QueryContextCallback to the QueryContextEvent.
|
||||
// TODO(.): Support removing QueryContextCallbacks?
|
||||
func (qe *QueryContextEvent) Add(cb QueryContextCallback) {
|
||||
*qe = append(*qe, cb)
|
||||
}
|
||||
|
||||
// Searches for a QueryContextCallback and returns the result of the first callback being able to deal with this
|
||||
// context, or Unknown if no such callback was found.
|
||||
func (qe QueryContextEvent) Call(v *View, key string, operator util.Op, operand interface{}, match_all bool) QueryContextReturn {
|
||||
log.Fine("Query context: %s, %v, %v, %v", key, operator, operand, match_all)
|
||||
for i := range qe {
|
||||
r := qe[i](v, key, operator, operand, match_all)
|
||||
if r != Unknown {
|
||||
return r
|
||||
}
|
||||
}
|
||||
log.Fine("Unknown context: %s", key)
|
||||
return Unknown
|
||||
}
|
||||
|
||||
// Add the provided WindowEventCallback to this WindowEvent.
|
||||
// TODO(.): Support removing WindowEventCallbacks?
|
||||
func (we *WindowEvent) Add(cb WindowEventCallback) {
|
||||
*we = append(*we, cb)
|
||||
}
|
||||
|
||||
// Trigger this WindowEvent by calling all the registered callbacks in order of registration.
|
||||
func (we *WindowEvent) Call(w *Window) {
|
||||
log.Finest("%s(%v)", wevNames[we], w.Id())
|
||||
for _, ev := range *we {
|
||||
ev(w)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
OnNew ViewEvent //< Called when a new view is created
|
||||
OnLoad ViewEvent //< Called when loading a view's buffer has finished
|
||||
OnActivated ViewEvent //< Called when a view gains input focus.
|
||||
OnDeactivated ViewEvent //< Called when a view loses input focus.
|
||||
OnPreClose ViewEvent //< Called when a view is about to be closed.
|
||||
OnClose ViewEvent //< Called when a view has been closed.
|
||||
OnPreSave ViewEvent //< Called just before a view's buffer is saved.
|
||||
OnPostSave ViewEvent //< Called after a view's buffer has been saved.
|
||||
OnModified ViewEvent //< Called when the contents of a view's underlying buffer has changed.
|
||||
OnSelectionModified ViewEvent //< Called when a view's Selection/cursor has changed.
|
||||
|
||||
OnNewWindow WindowEvent //< Called when a new window has been created.
|
||||
OnQueryContext QueryContextEvent //< Called when context is being queried.
|
||||
OnInit InitEvent //< Called once at program startup
|
||||
)
|
||||
|
||||
var (
|
||||
evNames = map[*ViewEvent]string{
|
||||
&OnNew: "OnNew",
|
||||
&OnLoad: "OnLoad",
|
||||
&OnActivated: "OnActivated",
|
||||
&OnDeactivated: "OnDeactivated",
|
||||
&OnPreClose: "OnPreClose",
|
||||
&OnClose: "OnClose",
|
||||
&OnPreSave: "OnPreSave",
|
||||
&OnPostSave: "OnPostSave",
|
||||
&OnModified: "OnModified",
|
||||
&OnSelectionModified: "OnSelectionModified",
|
||||
}
|
||||
wevNames = map[*WindowEvent]string{
|
||||
&OnNewWindow: "OnNewWindow",
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Register functionality dealing with a couple of built in contexts
|
||||
OnQueryContext.Add(func(v *View, key string, operator util.Op, operand interface{}, match_all bool) QueryContextReturn {
|
||||
if strings.HasPrefix(key, "setting.") && operator == util.OpEqual {
|
||||
c, ok := v.Settings().Get(key[8:]).(bool)
|
||||
if c && ok {
|
||||
return True
|
||||
}
|
||||
return False
|
||||
} else if key == "num_selections" {
|
||||
opf, _ := operand.(float64)
|
||||
op := int(opf)
|
||||
|
||||
switch operator {
|
||||
case util.OpEqual:
|
||||
if op == v.Sel().Len() {
|
||||
return True
|
||||
}
|
||||
return False
|
||||
case util.OpNotEqual:
|
||||
if op != v.Sel().Len() {
|
||||
return True
|
||||
}
|
||||
return False
|
||||
}
|
||||
}
|
||||
return Unknown
|
||||
})
|
||||
|
||||
OnLoad.Add(func(v *View) {
|
||||
GetEditor().Watch(v.Buffer().FileName(), v)
|
||||
})
|
||||
}
|
@ -1,145 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package backend
|
||||
|
||||
import (
|
||||
. "github.com/limetext/text"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOnSelectionModified(t *testing.T) {
|
||||
var res *RegionSet
|
||||
|
||||
callCount := 0
|
||||
|
||||
w := GetEditor().NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
v := w.NewFile()
|
||||
defer func() {
|
||||
v.SetScratch(true)
|
||||
v.Close()
|
||||
}()
|
||||
|
||||
OnSelectionModified.Add(func(v *View) {
|
||||
res = v.Sel()
|
||||
callCount++
|
||||
})
|
||||
|
||||
edit := v.BeginEdit()
|
||||
v.Insert(edit, 0, "abcd")
|
||||
v.EndEdit(edit)
|
||||
if callCount != 1 {
|
||||
t.Fatalf("%d != 1", callCount)
|
||||
}
|
||||
if !reflect.DeepEqual(res.Regions(), []Region{{4, 4}}) {
|
||||
t.Errorf("%v", res.Regions())
|
||||
}
|
||||
|
||||
edit = v.BeginEdit()
|
||||
v.Sel().Adjust(4, -1)
|
||||
v.EndEdit(edit)
|
||||
|
||||
if callCount != 2 {
|
||||
t.Fatalf("%d != 2", callCount)
|
||||
}
|
||||
if !reflect.DeepEqual(res.Regions(), []Region{{3, 3}}) {
|
||||
t.Errorf("%v", res.Regions())
|
||||
}
|
||||
|
||||
edit = v.BeginEdit()
|
||||
v.EndEdit(edit)
|
||||
|
||||
if callCount != 2 {
|
||||
t.Fatalf("%d != 2", callCount)
|
||||
}
|
||||
if !reflect.DeepEqual(res.Regions(), []Region{{3, 3}}) {
|
||||
t.Errorf("%v", res.Regions())
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnPreSave(t *testing.T) {
|
||||
testfile := "testdata/test_event.txt"
|
||||
callCount := 0
|
||||
|
||||
w := GetEditor().NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
v := w.NewFile()
|
||||
defer v.Close()
|
||||
|
||||
OnPreSave.Add(func(v *View) {
|
||||
callCount++
|
||||
})
|
||||
edit := v.BeginEdit()
|
||||
v.Insert(edit, 0, "abcd")
|
||||
v.EndEdit(edit)
|
||||
if err := v.SaveAs(testfile); err != nil {
|
||||
t.Fatal("Could not save the view")
|
||||
}
|
||||
if callCount != 1 {
|
||||
t.Fatalf("%d != 1", callCount)
|
||||
}
|
||||
v.Buffer().SetFileName(testfile)
|
||||
if err := v.Save(); err != nil {
|
||||
t.Fatalf("Could not save the view %s", err)
|
||||
}
|
||||
if callCount != 2 {
|
||||
t.Fatalf("%d != 2", callCount)
|
||||
}
|
||||
if err := os.Remove(testfile); err != nil {
|
||||
t.Errorf("Couldn't remove test file %s", testfile)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnPostSave(t *testing.T) {
|
||||
testfile := "testdata/test_event.txt"
|
||||
callCount := 0
|
||||
|
||||
w := GetEditor().NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
v := w.NewFile()
|
||||
defer v.Close()
|
||||
|
||||
OnPostSave.Add(func(v *View) {
|
||||
callCount++
|
||||
})
|
||||
edit := v.BeginEdit()
|
||||
v.Insert(edit, 0, "abcd")
|
||||
v.EndEdit(edit)
|
||||
if err := v.SaveAs(testfile); err != nil {
|
||||
t.Fatal("Could not save the view")
|
||||
}
|
||||
if callCount != 1 {
|
||||
t.Fatalf("%d != 1", callCount)
|
||||
}
|
||||
if err := v.Save(); err != nil {
|
||||
t.Fatalf("Could not save the view: %s", err)
|
||||
}
|
||||
if callCount != 2 {
|
||||
t.Fatalf("%d != 2", callCount)
|
||||
}
|
||||
if err := os.Remove(testfile); err != nil {
|
||||
t.Errorf("Couldn't remove test file %s", testfile)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnNewWindow(t *testing.T) {
|
||||
callCount := 0
|
||||
|
||||
OnNewWindow.Add(func(w *Window) {
|
||||
callCount++
|
||||
})
|
||||
|
||||
w := GetEditor().NewWindow()
|
||||
defer w.Close()
|
||||
|
||||
if callCount != 1 {
|
||||
t.Fatalf("%d != 1", callCount)
|
||||
}
|
||||
}
|
@ -1,140 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package keys
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type (
|
||||
Key rune
|
||||
)
|
||||
|
||||
const (
|
||||
Left Key = 0x2190 + iota
|
||||
Up
|
||||
Right
|
||||
Down
|
||||
|
||||
Enter = '\n'
|
||||
Escape = 0x001B
|
||||
Backspace = 0x0008
|
||||
Delete = 0x007F
|
||||
KeypadEnter = '\n'
|
||||
)
|
||||
|
||||
const (
|
||||
// map to dingbats...
|
||||
F1 Key = 0x2701 + iota
|
||||
F2
|
||||
F3
|
||||
F4
|
||||
F5
|
||||
F6
|
||||
F7
|
||||
F8
|
||||
F9
|
||||
F10
|
||||
F11
|
||||
F12
|
||||
Insert
|
||||
PageUp
|
||||
PageDown
|
||||
Home
|
||||
End
|
||||
Break
|
||||
Any Key = unicode.MaxRune
|
||||
)
|
||||
|
||||
const (
|
||||
shift = (1 << (29 - iota))
|
||||
ctrl
|
||||
alt
|
||||
super
|
||||
)
|
||||
|
||||
var keylut = map[string]Key{
|
||||
"up": Up,
|
||||
"left": Left,
|
||||
"right": Right,
|
||||
"down": Down,
|
||||
"enter": Enter,
|
||||
"tab": '\t',
|
||||
"escape": Escape,
|
||||
"space": ' ',
|
||||
"f1": F1,
|
||||
"f2": F2,
|
||||
"f3": F3,
|
||||
"f4": F4,
|
||||
"f5": F5,
|
||||
"f6": F6,
|
||||
"f7": F7,
|
||||
"f8": F8,
|
||||
"f9": F9,
|
||||
"f10": F10,
|
||||
"f11": F11,
|
||||
"f12": F12,
|
||||
"backspace": Backspace,
|
||||
"delete": Delete,
|
||||
"keypad_enter": KeypadEnter,
|
||||
"insert": Insert,
|
||||
"pageup": PageUp,
|
||||
"pagedown": PageDown,
|
||||
"home": Home,
|
||||
"end": End,
|
||||
"break": Break,
|
||||
"forward_slash": '/',
|
||||
"backquote": '`',
|
||||
"\\\"": '"',
|
||||
"plus": '+',
|
||||
"minus": '-',
|
||||
"equals": '=',
|
||||
"<character>": Any,
|
||||
}
|
||||
|
||||
var rkeylut = map[Key]string{
|
||||
Up: "up",
|
||||
Left: "left",
|
||||
Right: "right",
|
||||
Down: "down",
|
||||
Enter: "enter",
|
||||
'\t': "tab",
|
||||
Escape: "escape",
|
||||
' ': "space",
|
||||
F1: "f1",
|
||||
F2: "f2",
|
||||
F3: "f3",
|
||||
F4: "f4",
|
||||
F5: "f5",
|
||||
F6: "f6",
|
||||
F7: "f7",
|
||||
F8: "f8",
|
||||
F9: "f9",
|
||||
F10: "f10",
|
||||
F11: "f11",
|
||||
F12: "f12",
|
||||
Backspace: "backspace",
|
||||
Delete: "delete",
|
||||
Insert: "insert",
|
||||
PageUp: "pageup",
|
||||
PageDown: "pagedown",
|
||||
Home: "home",
|
||||
End: "end",
|
||||
Break: "break",
|
||||
'/': "forward_slash",
|
||||
'`': "backquote",
|
||||
'"': "\\\"",
|
||||
'+': "plus",
|
||||
'-': "minus",
|
||||
'=': "equals",
|
||||
Any: "<character>",
|
||||
}
|
||||
|
||||
func (k Key) String() string {
|
||||
if v, ok := rkeylut[k]; ok {
|
||||
return v
|
||||
}
|
||||
return string(k)
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package keys
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestKeyString(t *testing.T) {
|
||||
k := Key('i')
|
||||
if k.String() != "i" {
|
||||
t.Errorf("Expected %q but got %q", "i", k.String())
|
||||
}
|
||||
|
||||
k = Key(Home)
|
||||
if k.String() != "home" {
|
||||
t.Errorf("Expected %q but got %q", "home", k.String())
|
||||
}
|
||||
}
|
@ -1,187 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package keys
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
. "github.com/limetext/lime/backend/util"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type (
|
||||
// A single KeyBinding for which after pressing the given
|
||||
// sequence of Keys, and the Context matches,
|
||||
// the Command will be invoked with the provided Args.
|
||||
KeyBinding struct {
|
||||
Keys []KeyPress
|
||||
Command string
|
||||
Args map[string]interface{}
|
||||
Context []KeyContext
|
||||
priority int
|
||||
}
|
||||
|
||||
// An utility struct that(same as HasSettings ) is typically embedded in
|
||||
// other type structs to make that type implement the KeyBindingsInterface
|
||||
HasKeyBindings struct {
|
||||
keybindings KeyBindings
|
||||
}
|
||||
|
||||
// Defines an interface for types that have keybindings
|
||||
KeyBindingsInterface interface {
|
||||
KeyBindings() *KeyBindings
|
||||
}
|
||||
|
||||
KeyBindings struct {
|
||||
Bindings []*KeyBinding
|
||||
seqIndex int // The index we are in a multiple key sequence keybinding
|
||||
parent KeyBindingsInterface
|
||||
}
|
||||
)
|
||||
|
||||
func (k *HasKeyBindings) KeyBindings() *KeyBindings {
|
||||
return &k.keybindings
|
||||
}
|
||||
|
||||
// Returns the number of KeyBindings.
|
||||
func (k *KeyBindings) Len() int {
|
||||
return len(k.Bindings)
|
||||
}
|
||||
|
||||
// Compares one KeyBinding to another for sorting purposes.
|
||||
func (k *KeyBindings) Less(i, j int) bool {
|
||||
return k.Bindings[i].Keys[k.seqIndex].Index() < k.Bindings[j].Keys[k.seqIndex].Index()
|
||||
}
|
||||
|
||||
// Swaps the two KeyBindings at the given positions.
|
||||
func (k *KeyBindings) Swap(i, j int) {
|
||||
k.Bindings[i], k.Bindings[j] = k.Bindings[j], k.Bindings[i]
|
||||
}
|
||||
|
||||
// Drops all KeyBindings that are a sequence of key presses less or equal
|
||||
// to the given number.
|
||||
func (k *KeyBindings) DropLessEqualKeys(count int) {
|
||||
for {
|
||||
for i := 0; i < len(k.Bindings); {
|
||||
if len(k.Bindings[i].Keys) <= count {
|
||||
k.Bindings[i] = k.Bindings[len(k.Bindings)-1]
|
||||
k.Bindings = k.Bindings[:len(k.Bindings)-1]
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
sort.Sort(k)
|
||||
if k.parent == nil {
|
||||
break
|
||||
}
|
||||
k = k.parent.KeyBindings()
|
||||
}
|
||||
}
|
||||
|
||||
func (k *KeyBindings) UnmarshalJSON(d []byte) error {
|
||||
if err := json.Unmarshal(d, &k.Bindings); err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range k.Bindings {
|
||||
k.Bindings[i].priority = i
|
||||
}
|
||||
k.DropLessEqualKeys(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *KeyBindings) SetParent(p KeyBindingsInterface) {
|
||||
k.parent = p
|
||||
// All parents and childs seqIndex must be equal
|
||||
p.KeyBindings().seqIndex = k.seqIndex
|
||||
}
|
||||
|
||||
func (k *KeyBindings) Parent() KeyBindingsInterface {
|
||||
return k.parent
|
||||
}
|
||||
|
||||
func (k *KeyBindings) filter(ki int, ret *KeyBindings) {
|
||||
for {
|
||||
idx := sort.Search(k.Len(), func(i int) bool {
|
||||
return k.Bindings[i].Keys[k.seqIndex].Index() >= ki
|
||||
})
|
||||
for i := idx; i < len(k.Bindings) && k.Bindings[i].Keys[k.seqIndex].Index() == ki; i++ {
|
||||
ret.Bindings = append(ret.Bindings, k.Bindings[i])
|
||||
}
|
||||
if k.parent == nil {
|
||||
break
|
||||
}
|
||||
k = k.parent.KeyBindings()
|
||||
if ret.parent == nil {
|
||||
ret.SetParent(new(HasKeyBindings))
|
||||
}
|
||||
ret = ret.parent.KeyBindings()
|
||||
}
|
||||
}
|
||||
|
||||
// Filters the KeyBindings, returning a new KeyBindings object containing
|
||||
// a subset of matches for the given key press.
|
||||
func (k *KeyBindings) Filter(kp KeyPress) (ret KeyBindings) {
|
||||
p := Prof.Enter("key.filter")
|
||||
defer p.Exit()
|
||||
|
||||
kp.fix()
|
||||
k.DropLessEqualKeys(k.seqIndex)
|
||||
ret.seqIndex = k.seqIndex + 1
|
||||
ki := kp.Index()
|
||||
|
||||
k.filter(ki, &ret)
|
||||
|
||||
if kp.IsCharacter() {
|
||||
k.filter(int(Any), &ret)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Tries to resolve all the current KeyBindings in k to a single
|
||||
// action. If any action is appropriate as determined by context,
|
||||
// the return value will be the specific KeyBinding that is possible
|
||||
// to execute now, otherwise it is nil.
|
||||
func (k *KeyBindings) Action(qc func(key string, operator Op, operand interface{}, match_all bool) bool) (kb *KeyBinding) {
|
||||
p := Prof.Enter("key.action")
|
||||
defer p.Exit()
|
||||
|
||||
for {
|
||||
for i := range k.Bindings {
|
||||
if len(k.Bindings[i].Keys) > k.seqIndex {
|
||||
// This key binding is of a key sequence longer than what is currently
|
||||
// probed for. For example, the binding is for the sequence ['a','b','c'], but
|
||||
// the user has only pressed ['a','b'] so far.
|
||||
continue
|
||||
}
|
||||
for _, c := range k.Bindings[i].Context {
|
||||
if !qc(c.Key, c.Operator, c.Operand, c.MatchAll) {
|
||||
goto skip
|
||||
}
|
||||
}
|
||||
if kb == nil || kb.priority < k.Bindings[i].priority {
|
||||
kb = k.Bindings[i]
|
||||
}
|
||||
skip:
|
||||
}
|
||||
if kb != nil || k.parent == nil {
|
||||
break
|
||||
}
|
||||
k = k.parent.KeyBindings()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (k *KeyBindings) SeqIndex() int {
|
||||
return k.seqIndex
|
||||
}
|
||||
|
||||
func (k KeyBindings) String() string {
|
||||
var buf bytes.Buffer
|
||||
for _, b := range k.Bindings {
|
||||
buf.WriteString(fmt.Sprintf("%+v\n", b))
|
||||
}
|
||||
return buf.String()
|
||||
}
|
@ -1,245 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package keys
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/limetext/lime/backend/loaders"
|
||||
"github.com/limetext/lime/backend/util"
|
||||
)
|
||||
|
||||
func TestLoadKeyBindingsFromJSON(t *testing.T) {
|
||||
tests := []string{
|
||||
"testdata/Default.sublime-keymap",
|
||||
}
|
||||
for i, fn := range tests {
|
||||
if d, err := ioutil.ReadFile(fn); err != nil {
|
||||
t.Errorf("Test %d: Couldn't load file %s: %s", i, fn, err)
|
||||
} else {
|
||||
var bindings KeyBindings
|
||||
if err := loaders.LoadJSON(d, &bindings); err != nil {
|
||||
t.Errorf("Test %d: Error on LoadJSON: %s", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalError(t *testing.T) {
|
||||
var bindings KeyBindings
|
||||
if err := bindings.UnmarshalJSON([]byte(``)); err == nil {
|
||||
t.Errorf("Expected error on loading empty string")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDropLessEqualKeys(t *testing.T) {
|
||||
fn := "testdata/Default.sublime-keymap"
|
||||
d, err := ioutil.ReadFile(fn)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't read %s: %s", fn, err)
|
||||
}
|
||||
|
||||
var bd KeyBindings
|
||||
if err = loaders.LoadJSON(d, &bd); err != nil {
|
||||
t.Fatalf("Error loading json: %s", err)
|
||||
}
|
||||
bd.DropLessEqualKeys(1)
|
||||
if cmd := bd.Bindings[0].Command; cmd != "test2" {
|
||||
t.Errorf("Expected Command %s, but got %s", "test2", cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetParent(t *testing.T) {
|
||||
fn := "testdata/Default.sublime-keymap"
|
||||
fnp := "testdata/test.sublime-keymap"
|
||||
var (
|
||||
bd KeyBindings
|
||||
p HasKeyBindings
|
||||
)
|
||||
|
||||
d, err := ioutil.ReadFile(fn)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't read %s: %s", fn, err)
|
||||
}
|
||||
if err = loaders.LoadJSON(d, &bd); err != nil {
|
||||
t.Fatalf("Error loading json: %s", err)
|
||||
}
|
||||
d, err = ioutil.ReadFile(fnp)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't read %s: %s", fn, err)
|
||||
}
|
||||
if err = loaders.LoadJSON(d, p.KeyBindings()); err != nil {
|
||||
t.Fatalf("Error loading json: %s", err)
|
||||
}
|
||||
|
||||
p.KeyBindings().seqIndex = 10
|
||||
bd.SetParent(&p)
|
||||
if bd.seqIndex != p.KeyBindings().seqIndex {
|
||||
t.Fatalf("Expected parent and child seqIndex be equal %d != %d", p.KeyBindings().seqIndex, bd.seqIndex)
|
||||
}
|
||||
|
||||
ret := bd.Filter(KeyPress{Key: 'd', Ctrl: true})
|
||||
if ret.Len() != 1 {
|
||||
t.Fatalf("Expected ret keyBindings len %d, but got %d", 1, ret.Len())
|
||||
}
|
||||
if ret.parent.KeyBindings().Len() != 1 {
|
||||
t.Fatalf("Expected ret parent keyBindings len %d, but got %d", 1, ret.parent.KeyBindings().Len())
|
||||
}
|
||||
if cmd := ret.Bindings[0].Command; cmd != "test4" {
|
||||
t.Errorf("Expected Command %s, but got %s", "test4", cmd)
|
||||
}
|
||||
if cmd := ret.parent.KeyBindings().Bindings[0].Command; cmd != "t1" {
|
||||
t.Errorf("Expected Command %s, but got %s", "t1", cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParent(t *testing.T) {
|
||||
fn := "testdata/Default.sublime-keymap"
|
||||
fnp := "testdata/test.sublime-keymap"
|
||||
var (
|
||||
bd KeyBindings
|
||||
p HasKeyBindings
|
||||
)
|
||||
|
||||
d, err := ioutil.ReadFile(fn)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't read %s: %s", fn, err)
|
||||
}
|
||||
if err = loaders.LoadJSON(d, &bd); err != nil {
|
||||
t.Fatalf("Error loading json: %s", err)
|
||||
}
|
||||
|
||||
d, err = ioutil.ReadFile(fnp)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't read %s: %s", fn, err)
|
||||
}
|
||||
if err = loaders.LoadJSON(d, p.KeyBindings()); err != nil {
|
||||
t.Fatalf("Error loading json: %s", err)
|
||||
}
|
||||
|
||||
bd.SetParent(&p)
|
||||
|
||||
if cmd := bd.Parent().KeyBindings().Bindings[0].Command; cmd != "t2" {
|
||||
t.Errorf("Expected Command %s, but got %s", "t2", cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyBindingsFilter(t *testing.T) {
|
||||
tests := []struct {
|
||||
kp KeyPress
|
||||
count int
|
||||
}{
|
||||
{
|
||||
KeyPress{Key: 'i', Ctrl: true},
|
||||
2,
|
||||
},
|
||||
{
|
||||
KeyPress{Key: 'i'},
|
||||
1,
|
||||
},
|
||||
}
|
||||
|
||||
if d, err := ioutil.ReadFile("testdata/Default.sublime-keymap"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
var bindings KeyBindings
|
||||
loaders.LoadJSON(d, &bindings)
|
||||
|
||||
for i, test := range tests {
|
||||
if b := bindings.Filter(test.kp); b.Len() != test.count {
|
||||
t.Errorf("Test %d: Expected %d bindings, but got %d", i, test.count, b.Len())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyBindingsAction(t *testing.T) {
|
||||
tests := []struct {
|
||||
kp KeyPress
|
||||
retNil bool
|
||||
ck string
|
||||
}{
|
||||
{
|
||||
KeyPress{Key: 'i'},
|
||||
false,
|
||||
"test3",
|
||||
},
|
||||
{
|
||||
KeyPress{Key: 'p'},
|
||||
false,
|
||||
"t2",
|
||||
},
|
||||
{
|
||||
KeyPress{Key: 'i', Ctrl: true},
|
||||
true,
|
||||
"",
|
||||
},
|
||||
{
|
||||
KeyPress{Key: 'c'},
|
||||
false,
|
||||
"t5",
|
||||
},
|
||||
}
|
||||
|
||||
if d, err := ioutil.ReadFile("testdata/Default.sublime-keymap"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
var (
|
||||
bindings KeyBindings
|
||||
p HasKeyBindings
|
||||
)
|
||||
loaders.LoadJSON(d, &bindings)
|
||||
|
||||
if d, err = ioutil.ReadFile("testdata/test.sublime-keymap"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
loaders.LoadJSON(d, p.KeyBindings())
|
||||
bindings.SetParent(&p)
|
||||
|
||||
for i, test := range tests {
|
||||
qc := func(key string, operator util.Op, operand interface{}, match_all bool) bool {
|
||||
return key == test.ck
|
||||
}
|
||||
b := bindings.Filter(test.kp)
|
||||
if a := b.Action(qc); test.retNil {
|
||||
if a != nil {
|
||||
t.Errorf("Test %d: Expected action to be nil but got %v", i, a)
|
||||
}
|
||||
} else if a.Context[0].Key != test.ck {
|
||||
t.Errorf("Test %d: Expected %s, but got %s", i, test.ck, a.Context[0].Key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeqIndex(t *testing.T) {
|
||||
var bd KeyBindings
|
||||
bd.seqIndex = 3
|
||||
if bd.SeqIndex() != 3 {
|
||||
t.Errorf("Expected SeqIndex %d, but got %d", 3, bd.SeqIndex())
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyBindingsString(t *testing.T) {
|
||||
fn := "testdata/test.sublime-keymap"
|
||||
var bd KeyBindings
|
||||
|
||||
d, err := ioutil.ReadFile(fn)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't read %s: %s", fn, err)
|
||||
}
|
||||
if err = loaders.LoadJSON(d, &bd); err != nil {
|
||||
t.Fatalf("Error loading json: %s", err)
|
||||
}
|
||||
|
||||
expected :=
|
||||
`&{Keys:[p] Command:t2 Args:map[] Context:[{rawKeyContext:{Key:t2 Operator:0 Operand:true MatchAll:false}}] priority:1}
|
||||
&{Keys:[ctrl+d ctrl+k] Command:t1 Args:map[] Context:[{rawKeyContext:{Key:t1 Operator:0 Operand:true MatchAll:false}}] priority:0}
|
||||
`
|
||||
if bd.String() != expected {
|
||||
t.Errorf("Expected String %s, but got %s", expected, bd.String())
|
||||
}
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package keys
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/limetext/lime/backend/util"
|
||||
)
|
||||
|
||||
type (
|
||||
// A Context definition for which a key binding
|
||||
// is to be considered.
|
||||
KeyContext struct {
|
||||
rawKeyContext
|
||||
}
|
||||
|
||||
// TODO(.): HACK. This is because I want to use the default UnmarshalJSON
|
||||
// behaviour on most of the struct member, but then also do some custom
|
||||
// handling too.
|
||||
//
|
||||
// So the publicly exported KeyContext implements a custom UnmarshalJSON,
|
||||
// which then invokes the default UnMarshalJSON handling on the embedded
|
||||
// rawKeyContext, and then does it's own custom code after that.
|
||||
//
|
||||
// Is there a better way to do this?
|
||||
rawKeyContext struct {
|
||||
Key string //< The context's name.
|
||||
Operator util.Op //< The operation to perform.
|
||||
Operand interface{} //< The operand on which this operation should be performed.
|
||||
MatchAll bool `json:"match_all"` //< Whether all selections should match the context or if it's enough for just one to match.
|
||||
}
|
||||
)
|
||||
|
||||
func (k *KeyContext) UnmarshalJSON(d []byte) error {
|
||||
if err := json.Unmarshal(d, &k.rawKeyContext); err != nil {
|
||||
return err
|
||||
}
|
||||
if k.Operand == nil {
|
||||
k.Operand = true
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package keys
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestKeyContextUnmarshalError(t *testing.T) {
|
||||
var context KeyContext
|
||||
if err := context.UnmarshalJSON([]byte(``)); err == nil {
|
||||
t.Errorf("Expected error on loading empty string")
|
||||
}
|
||||
}
|
@ -1,104 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package keys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/limetext/lime/backend/log"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// KeyPress describes a key press event.
|
||||
// Note that Key does not distinguish between capital and non-capital letters;
|
||||
// use the Text property for this purpose.
|
||||
type KeyPress struct {
|
||||
Text string // the text representation of the key
|
||||
Key Key // the code for the key that was pressed
|
||||
Shift, Super, Alt, Ctrl bool // true if modifier key was pressed
|
||||
}
|
||||
|
||||
// Returns an index used for sorting key presses.
|
||||
// TODO(.): This is in no way a unique index with quite a lot of collisions and potentially resulting
|
||||
// in bad lookups.
|
||||
func (k KeyPress) Index() (ret int) {
|
||||
ret = int(k.Key)
|
||||
if k.Shift {
|
||||
ret += shift
|
||||
}
|
||||
if k.Alt {
|
||||
ret += alt
|
||||
}
|
||||
if k.Ctrl {
|
||||
ret += ctrl
|
||||
}
|
||||
if k.Super {
|
||||
ret += super
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Returns whether this KeyPress is a print character or not.
|
||||
func (k KeyPress) IsCharacter() bool {
|
||||
return unicode.IsPrint(rune(k.Key)) && !k.Super && !k.Ctrl
|
||||
}
|
||||
|
||||
// Modifies the KeyPress so that it's Key is a unicode lower case
|
||||
// rune and if it was in uppercase before this modification, the
|
||||
// "Shift" modifier is also enabled.
|
||||
func (k *KeyPress) fix() {
|
||||
lower := Key(unicode.ToLower(rune(k.Key)))
|
||||
if lower != k.Key {
|
||||
k.Shift = true
|
||||
k.Key = lower
|
||||
}
|
||||
}
|
||||
|
||||
func (k *KeyPress) UnmarshalJSON(d []byte) error {
|
||||
combo := strings.Split(string(d[1:len(d)-1]), "+")
|
||||
for _, c := range combo {
|
||||
lower := strings.ToLower(c)
|
||||
switch lower {
|
||||
case "super":
|
||||
k.Super = true
|
||||
case "ctrl":
|
||||
k.Ctrl = true
|
||||
case "alt":
|
||||
k.Alt = true
|
||||
case "shift":
|
||||
k.Shift = true
|
||||
default:
|
||||
if v, ok := keylut[lower]; ok {
|
||||
k.Key = v
|
||||
} else {
|
||||
r := []Key(c)
|
||||
if len(r) != 1 {
|
||||
log.Warn("Unknown key value with %d bytes: %s", len(c), c)
|
||||
return nil
|
||||
}
|
||||
k.Key = Key(c[0])
|
||||
k.fix()
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k KeyPress) String() (ret string) {
|
||||
if k.Super {
|
||||
ret += "super+"
|
||||
}
|
||||
if k.Ctrl {
|
||||
ret += "ctrl+"
|
||||
}
|
||||
if k.Alt {
|
||||
ret += "alt+"
|
||||
}
|
||||
if k.Shift {
|
||||
ret += "shift+"
|
||||
}
|
||||
ret += fmt.Sprintf("%s", k.Key)
|
||||
return
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package keys
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestKeyPressIndex(t *testing.T) {
|
||||
tests := []struct {
|
||||
kp KeyPress
|
||||
exp int
|
||||
}{
|
||||
{
|
||||
KeyPress{Key: 'a', Shift: false, Super: false, Alt: false, Ctrl: false},
|
||||
int('a'),
|
||||
},
|
||||
{
|
||||
KeyPress{Key: 'a', Shift: true, Super: false, Alt: false, Ctrl: false},
|
||||
int('a') + shift,
|
||||
},
|
||||
{
|
||||
KeyPress{Key: 'a', Shift: true, Super: true, Alt: false, Ctrl: false},
|
||||
int('a') + shift + super,
|
||||
},
|
||||
{
|
||||
KeyPress{Key: 'a', Shift: true, Super: true, Alt: true, Ctrl: false},
|
||||
int('a') + shift + super + alt,
|
||||
},
|
||||
{
|
||||
KeyPress{Key: 'a', Shift: true, Super: true, Alt: true, Ctrl: true},
|
||||
int('a') + shift + super + alt + ctrl,
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
if test.kp.Index() != test.exp {
|
||||
t.Errorf("Test %d: Expected %d, but got %d", i, test.exp, test.kp.Index())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyPressIsCharacter(t *testing.T) {
|
||||
tests := []struct {
|
||||
kp KeyPress
|
||||
exp bool
|
||||
}{
|
||||
{
|
||||
KeyPress{Key: 'a', Shift: false, Super: false, Alt: false, Ctrl: false},
|
||||
true,
|
||||
},
|
||||
{
|
||||
KeyPress{Key: 'a', Shift: true, Super: false, Alt: false, Ctrl: false},
|
||||
true,
|
||||
},
|
||||
{
|
||||
KeyPress{Key: 'a', Shift: false, Super: true, Alt: false, Ctrl: false},
|
||||
false,
|
||||
},
|
||||
{
|
||||
KeyPress{Key: 'a', Shift: false, Super: false, Alt: false, Ctrl: true},
|
||||
false,
|
||||
},
|
||||
// {
|
||||
// KeyPress{Key: F1, Shift: false, Super: false, Alt: false, Ctrl: false},
|
||||
// false,
|
||||
// },
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
if test.kp.IsCharacter() != test.exp {
|
||||
t.Errorf("Test %d: Expected %v, but got %v", i, test.exp, test.kp.IsCharacter())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyPressFix(t *testing.T) {
|
||||
k := KeyPress{"A", 'A', false, false, false, false}
|
||||
k.fix()
|
||||
if k.Key != 'a' {
|
||||
t.Errorf("Expected the key to be %q, but it was %q", 'a', k.Key)
|
||||
}
|
||||
if !k.Shift {
|
||||
t.Error("Expected the shift modifier to be active, but it wasn't")
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyPressUnmarshalJSON(t *testing.T) {
|
||||
var k KeyPress
|
||||
d := `"super+ctrl+alt+shift+f1+λλλ"`
|
||||
err := k.UnmarshalJSON([]byte(d))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyPressString(t *testing.T) {
|
||||
k1 := KeyPress{"a", 'a', true, true, false, false}
|
||||
if k1.String() != "super+shift+a" {
|
||||
t.Errorf("Expected %q, but got %q", "super+shift+a", k1.String())
|
||||
}
|
||||
|
||||
k2 := KeyPress{"b", 'b', true, false, true, true}
|
||||
if k2.String() != "ctrl+alt+shift+b" {
|
||||
t.Errorf("Expected %q, but got %q", "ctrl+alt+shift+b", k2.String())
|
||||
}
|
||||
}
|
8
backend/keys/testdata/Default.sublime-keymap
vendored
8
backend/keys/testdata/Default.sublime-keymap
vendored
@ -1,8 +0,0 @@
|
||||
[
|
||||
{ "keys": ["ctrl+i", "j"], "command": "test1", "context": [{ "key": "test1" }] },
|
||||
{ "keys": ["ctrl+i", "k"], "command": "test2", "context": [{ "key": "test2" }] },
|
||||
{ "keys": ["i"], "command": "test3", "context": [{ "key": "test3" }] },
|
||||
{ "keys": ["ctrl+d"], "command": "test4", "context": [{ "key": "test4" }] },
|
||||
{ "keys": ["c"], "command": "test5", "context": [{ "key": "t5" }] },
|
||||
{ "keys": ["c"], "command": "test5", "context": [{ "key": "test5" }] },
|
||||
]
|
4
backend/keys/testdata/test.sublime-keymap
vendored
4
backend/keys/testdata/test.sublime-keymap
vendored
@ -1,4 +0,0 @@
|
||||
[
|
||||
{ "keys": ["ctrl+d", "ctrl+k"], "command": "t1", "context": [{ "key": "t1" }] },
|
||||
{ "keys": ["p"], "command": "t2", "context": [{ "key": "t2" }] },
|
||||
]
|
@ -1,76 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package loaders
|
||||
|
||||
import (
|
||||
sj "encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/limetext/lime/backend/loaders/json"
|
||||
. "github.com/limetext/text"
|
||||
)
|
||||
|
||||
type regionSetAdjuster struct {
|
||||
Set *RegionSet
|
||||
}
|
||||
|
||||
func (adjuster *regionSetAdjuster) Erased(changed_buffer Buffer, region_removed Region, data_removed []rune) {
|
||||
adjuster.Set.Adjust(region_removed.B, region_removed.A-region_removed.B)
|
||||
}
|
||||
|
||||
func (adjuster *regionSetAdjuster) Inserted(changed_buffer Buffer, region_inserted Region, data_inserted []rune) {
|
||||
adjuster.Set.Adjust(region_inserted.A, region_inserted.B-region_inserted.A)
|
||||
}
|
||||
|
||||
func LoadJSON(data []byte, intf interface{}) error {
|
||||
var (
|
||||
b = NewBuffer()
|
||||
p json.JSON
|
||||
set RegionSet
|
||||
)
|
||||
defer b.Close()
|
||||
str := string(data)
|
||||
b.Insert(0, str)
|
||||
|
||||
// Lime works with rune indices, but the parser works with byte indices
|
||||
// so we need to create a lookup table to map these correctly.
|
||||
lut := make([]int, len(str))
|
||||
runeidx := 0
|
||||
for i := range str {
|
||||
lut[i] = runeidx
|
||||
runeidx++
|
||||
}
|
||||
|
||||
if !p.Parse(str) {
|
||||
return fmt.Errorf("%s, %s", p.Error(), p.RootNode())
|
||||
}
|
||||
root := p.RootNode()
|
||||
for _, child := range root.Children {
|
||||
switch child.Name {
|
||||
case "BlockComment", "LineComment", "EndOfFile", "JunkComma":
|
||||
if child.Range.End() < len(lut) {
|
||||
set.Add(Region{lut[child.Range.Begin()], lut[child.Range.End()]})
|
||||
}
|
||||
default:
|
||||
return errors.New("Unhandled node: " + child.Name)
|
||||
}
|
||||
}
|
||||
b.AddObserver(®ionSetAdjuster{Set: &set})
|
||||
i := 0
|
||||
for {
|
||||
l := set.Len()
|
||||
if i >= l {
|
||||
break
|
||||
}
|
||||
r := set.Get(i)
|
||||
b.Erase(r.Begin(), r.Size())
|
||||
if l2 := set.Len(); l2 != l {
|
||||
continue
|
||||
}
|
||||
i++
|
||||
}
|
||||
// TODO(q): Map any line/column errors to the actual file's line/column
|
||||
return sj.Unmarshal([]byte(b.Substr(Region{0, b.Size()})), intf)
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
# Sublime Text allows "//" and "/* */" style comments as well as extra commas, so isn't "pure" json
|
||||
|
||||
JsonFile <- Values EndOfFile?
|
||||
Values <- Spacing? Value Spacing? (',' Spacing? Value Spacing?)* JunkComma
|
||||
Value <- (Dictionary / Array / QuotedText / Float / Integer / Boolean / Null)
|
||||
Null <- "null"
|
||||
Dictionary <- '{' KeyValuePairs* Spacing? JunkComma '}'
|
||||
Array <- '[' Values* Spacing? ']'
|
||||
KeyValuePairs <- Spacing? KeyValuePair Spacing? (',' Spacing? KeyValuePair Spacing?)*
|
||||
KeyValuePair <- QuotedText ':' Spacing? Value
|
||||
QuotedText <- '"' Text? '"'
|
||||
Text <- &'"' / ('\\' . / (!'"' .))+
|
||||
Integer <- '-'? '0' ![0-9] / '-'? [1-9] [0-9]*
|
||||
Float <- '-'? [0-9]* '.' [0-9]+ ([Ee] [-+]? [0-9]*)? / '-'? [0-9]+ [Ee] [-+]? [0-9]+
|
||||
Boolean <- "true" / "false"
|
||||
Spacing <- (Comment / [ \t\n\r])+
|
||||
EndOfFile <- !.
|
||||
|
||||
JunkComma <- (Spacing? ',' Spacing?)?
|
||||
Comment <- LineComment / BlockComment
|
||||
LineComment <- "//" (![\n\r] .)* [\n\r]
|
||||
BlockComment <- "/*" (!"*/" .)* "*/"
|
File diff suppressed because it is too large
Load Diff
@ -1,87 +0,0 @@
|
||||
// Copyright 2015 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ = time.Time{}
|
||||
|
||||
const testname = "testdata/Default (OSX).sublime-keymap"
|
||||
|
||||
func loadData(path string) (retdata string, err error) {
|
||||
var data []byte
|
||||
if strings.HasPrefix(path, "http://") {
|
||||
p2 := strings.Replace(strings.Replace(path, "http://", "http_", -1), "/", "_", -1)
|
||||
if retdata, err = loadData(p2); err != nil {
|
||||
if res, err := http.Get(path); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
defer res.Body.Close()
|
||||
if data, err = ioutil.ReadAll(res.Body); err != nil {
|
||||
return "", err
|
||||
} else if err = ioutil.WriteFile(p2, data, 0644); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return loadData(p2)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} else if strings.HasSuffix(path, ".zip") {
|
||||
if zf, err := zip.OpenReader(path); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
defer zf.Close()
|
||||
f, err := zf.File[0].Open()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
if data, err = ioutil.ReadAll(f); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data, err = ioutil.ReadFile(path)
|
||||
}
|
||||
return string(data), err
|
||||
}
|
||||
|
||||
func TestParser(t *testing.T) {
|
||||
var p JSON
|
||||
if data, err := loadData(testname); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
root := p.RootNode()
|
||||
if !p.Parse(data) {
|
||||
|
||||
t.Fatalf("Didn't parse correctly: %s\n", p.Error())
|
||||
} else {
|
||||
|
||||
if root.Range.B != p.ParserData.Len() {
|
||||
t.Fatalf("Parsing didn't finish: %v\n%s", root, p.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParser(b *testing.B) {
|
||||
var p JSON
|
||||
if data, err := loadData(testname); err != nil {
|
||||
b.Fatal(err)
|
||||
} else {
|
||||
for i := 0; i < b.N; i++ {
|
||||
p.Parse(data)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
[
|
||||
{ "keys": ["super+shift+7"], "command": "toggle_comment", "args": { "block": false } },
|
||||
{ "keys": ["super+shift+r"], "command": "show_overlay", "args": {"overlay": "goto", "text": "#"} },
|
||||
//{ "keys": ["super+shift+v"], "command": "paste" },
|
||||
//{ "keys": ["super+v"], "command": "paste_and_indent" },
|
||||
{ "keys": ["ctrl+§"], "command": "show_panel", "args": {"panel": "console", "toggle": true} },
|
||||
{ "keys": ["super+shift+b"], "command": "show_panel", "args": {"panel": "output.exec", "toggle": true} },
|
||||
{ "keys": ["super+shift+w"], "command": "close_all" },
|
||||
|
||||
{ "keys": ["ctrl+k"], "command": "move", "args": {"by": "lines", "forward": false} },
|
||||
/*
|
||||
// { "keys": ["ctrl+j"], "command": "move", "args": {"by": "lines", "forward": true} },
|
||||
// /*/
|
||||
{ "keys": ["ctrl+alt+f"], "command": "astyleformat" },
|
||||
{ "keys": ["super+alt+f"], "command": "show_panel", "args": {"panel": "replace"} },
|
||||
|
||||
//{"keys": ["ctrl+t"], "command": "toggle_setting", "args": {"setting": "vintage_lines.force_mode"}},
|
||||
|
||||
{"keys": ["ctrl+j", "d"], "command": "open_java_doc", "args": {"under_cursor": true }},
|
||||
{"keys": ["ctrl+j", "s"], "command": "open_java_source", "args": {"under_cursor": true }},
|
||||
{"keys": ["ctrl+j", "i"], "command": "import_java_class"},
|
||||
{
|
||||
"keys": ["ctrl+r"],
|
||||
"command": "gs_shell",
|
||||
"args": {"run": "go play"},
|
||||
"context": [{ "key": "selector", "operator": "equal", "operand": "source.go" }]
|
||||
},
|
||||
|
||||
{"keys": ["ctrl+alt+s"], "command": "toggle_setting", "args": {"setting": "adb_auto_scroll"}},
|
||||
|
||||
// {"keys": ["ctrl+t"], "command": "set_setting", "args": {"setting": "draw_white_space", "value": "all"},
|
||||
// "context": [{ "key": "setting.draw_white_space", "operator": "not_equal", "operand": "all" }]
|
||||
// },
|
||||
// {"keys": ["ctrl+t"], "command": "set_setting", "args": {"setting": "draw_white_space", "value": "none"},
|
||||
// "context": [{ "key": "setting.draw_white_space", "operator": "equal", "operand": "all" }]
|
||||
// },
|
||||
|
||||
// { "keys": ["alt+o"], "command": "my_open_file"},
|
||||
// { "keys": ["ctrl+b"], "command": "wrap_text", "args": {"tag": "b"}},
|
||||
// { "keys": ["alt+i"], "command": "wrap_text", "args": {"start": "<img src=\"", "end": "\"/>"}},
|
||||
/*
|
||||
|
||||
{ "keys": ["tab"], "command": "insert", "args": {"characters": "\t"} },
|
||||
{ "keys": ["tab"], "command": "insert_best_completion", "args": {"default": "\t", "exact": true} },
|
||||
|
||||
{ "keys": ["tab"], "command": "indent", "context":
|
||||
[
|
||||
{ "key": "text", "operator": "regex_contains", "operand": "\n" }
|
||||
]
|
||||
},
|
||||
{ "keys": ["tab"], "command": "next_field", "context":
|
||||
[
|
||||
{ "key": "has_next_field", "operator": "equal", "operand": true }
|
||||
]
|
||||
},
|
||||
{ "keys": ["tab"], "command": "auto_complete", "context":
|
||||
[{ "key": "preceding_text", "operator": "regex_contains", "operand": "[a-zA-Z0-9_]+$", "match_all": true },
|
||||
{ "key": "auto_complete_visible", "operator": "equal", "operand": false},
|
||||
{ "key": "last_command", "operator": "not_equal", "operand": "commit_completion" },
|
||||
{ "key": "last_command", "operator": "not_equal", "operand": "auto_complete" },
|
||||
{ "key": "last_command", "operator": "not_equal", "operand": "insert_snippet" },
|
||||
{ "key": "has_next_field", "operator": "equal", "operand": false },
|
||||
{ "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }
|
||||
]
|
||||
},
|
||||
|
||||
{ "keys": ["tab"], "command": "commit_completion", "context":
|
||||
[
|
||||
{ "key": "auto_complete_visible" },
|
||||
{ "key": "setting.auto_complete_commit_on_tab" }
|
||||
]
|
||||
},
|
||||
*/
|
||||
|
||||
{ "keys": ["enter"], "command": "commit_completion", "context": [{ "key": "auto_complete_visible" }] },
|
||||
{ "keys": ["super+q"], "command": "nop"},
|
||||
{ "keys": ["super+m"], "command": "toggle_minimap"}
|
||||
|
||||
]
|
@ -1,5 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package loaders
|
@ -1,78 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package loaders
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/limetext/lime/backend/loaders/plist"
|
||||
"github.com/quarnster/parser"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func plistconv(buf *bytes.Buffer, node *parser.Node) error {
|
||||
switch node.Name {
|
||||
case "Key":
|
||||
buf.WriteString("\"" + node.Data() + "\": ")
|
||||
case "String":
|
||||
n := node.Data()
|
||||
n = strings.Replace(n, "\\", "\\\\", -1)
|
||||
n = strings.Replace(n, "\"", "\\\"", -1)
|
||||
n = strings.Replace(n, "\n", "\\n", -1)
|
||||
n = strings.Replace(n, "\t", "\\t", -1)
|
||||
n = strings.Replace(n, ">", ">", -1)
|
||||
n = strings.Replace(n, "<", "<", -1)
|
||||
buf.WriteString("\"" + n + "\"")
|
||||
case "Dictionary":
|
||||
buf.WriteString("{\n\t")
|
||||
for i, child := range node.Children {
|
||||
if i != 0 && i&1 == 0 {
|
||||
buf.WriteString(",\n\t")
|
||||
}
|
||||
if err := plistconv(buf, child); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString("}\n")
|
||||
case "Array":
|
||||
buf.WriteString("[\n\t")
|
||||
for i, child := range node.Children {
|
||||
if i != 0 {
|
||||
buf.WriteString(",\n\t")
|
||||
}
|
||||
|
||||
if err := plistconv(buf, child); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString("]\n\t")
|
||||
case "EndOfFile":
|
||||
default:
|
||||
return errors.New(fmt.Sprintf("Unhandled node: %s", node.Name))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadPlist(data []byte, intf interface{}) error {
|
||||
var (
|
||||
p plist.PLIST
|
||||
)
|
||||
if !p.Parse(strings.Replace(string(data), "\r", "", -1)) {
|
||||
return p.Error()
|
||||
}
|
||||
var (
|
||||
root = p.RootNode()
|
||||
buf bytes.Buffer
|
||||
)
|
||||
for _, child := range root.Children {
|
||||
if err := plistconv(&buf, child); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return LoadJSON(buf.Bytes(), intf)
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
PlistFile <- "<?xml" (!"?>" .)+ "?>" Spacing* "<!DOCTYPE" (!'>' .)+ '>' Spacing* Plist Spacing* EndOfFile?
|
||||
Plist <- "<plist version=\"1.0\">" Values "</plist>"
|
||||
|
||||
Dictionary <- "<dict>" KeyValuePair+ "</dict>" / "<dict/>"
|
||||
KeyValuePair <- Spacing* KeyTag Spacing* Value Spacing*
|
||||
KeyTag <- "<key>" Key "</key>"
|
||||
Key <- (!'<' .)*
|
||||
StringTag <- "<string>" String "</string>"
|
||||
String <- (!'<' .)*
|
||||
Value <- Array / StringTag / Dictionary
|
||||
Values <- (Spacing* Value Spacing*)*
|
||||
Array <- "<array>" Values "</array>" / "<array/>"
|
||||
|
||||
Spacing <- [ \t\n\r]+
|
||||
EndOfFile <- !.
|
@ -1,911 +0,0 @@
|
||||
// Copyright 2015 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
// This file was generated by tasks/build/gen_loaders.go and shouldn't be manually modified
|
||||
|
||||
package plist
|
||||
|
||||
import (
|
||||
"github.com/limetext/text"
|
||||
. "github.com/quarnster/parser"
|
||||
)
|
||||
|
||||
type PLIST struct {
|
||||
ParserData Reader
|
||||
IgnoreRange text.Region
|
||||
Root Node
|
||||
LastError int
|
||||
}
|
||||
|
||||
func (p *PLIST) RootNode() *Node {
|
||||
return &p.Root
|
||||
}
|
||||
|
||||
func (p *PLIST) SetData(data string) {
|
||||
p.ParserData = NewReader(data)
|
||||
p.Root = Node{Name: "PLIST", P: p}
|
||||
p.IgnoreRange = text.Region{}
|
||||
p.LastError = 0
|
||||
}
|
||||
|
||||
func (p *PLIST) Parse(data string) bool {
|
||||
p.SetData(data)
|
||||
ret := p.realParse()
|
||||
p.Root.UpdateRange()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (p *PLIST) Data(start, end int) string {
|
||||
return p.ParserData.Substring(start, end)
|
||||
}
|
||||
|
||||
func (p *PLIST) Error() Error {
|
||||
errstr := ""
|
||||
line, column := p.ParserData.LineCol(p.LastError)
|
||||
|
||||
if p.LastError == p.ParserData.Len() {
|
||||
errstr = "Unexpected EOF"
|
||||
} else {
|
||||
p.ParserData.Seek(p.LastError)
|
||||
if r := p.ParserData.Read(); r == '\r' || r == '\n' {
|
||||
errstr = "Unexpected new line"
|
||||
} else {
|
||||
errstr = "Unexpected " + string(r)
|
||||
}
|
||||
}
|
||||
return NewError(line, column, errstr)
|
||||
}
|
||||
|
||||
func (p *PLIST) realParse() bool {
|
||||
return p.PlistFile()
|
||||
}
|
||||
func (p *PLIST) PlistFile() bool {
|
||||
// PlistFile <- "<?xml" (!"?>" .)+ "?>" Spacing* "<!DOCTYPE" (!'>' .)+ '>' Spacing* Plist Spacing* EndOfFile?
|
||||
accept := false
|
||||
accept = true
|
||||
start := p.ParserData.Pos()
|
||||
{
|
||||
save := p.ParserData.Pos()
|
||||
{
|
||||
accept = true
|
||||
s := p.ParserData.Pos()
|
||||
if p.ParserData.Read() != '<' || p.ParserData.Read() != '?' || p.ParserData.Read() != 'x' || p.ParserData.Read() != 'm' || p.ParserData.Read() != 'l' {
|
||||
p.ParserData.Seek(s)
|
||||
accept = false
|
||||
}
|
||||
}
|
||||
if accept {
|
||||
{
|
||||
save := p.ParserData.Pos()
|
||||
{
|
||||
save := p.ParserData.Pos()
|
||||
s := p.ParserData.Pos()
|
||||
{
|
||||
accept = true
|
||||
s := p.ParserData.Pos()
|
||||
if p.ParserData.Read() != '?' || p.ParserData.Read() != '>' {
|
||||
p.ParserData.Seek(s)
|
||||
accept = false
|
||||
}
|
||||
}
|
||||
p.ParserData.Seek(s)
|
||||
p.Root.Discard(s)
|
||||
accept = !accept
|
||||
if accept {
|
||||
if p.ParserData.Pos() >= p.ParserData.Len() {
|
||||
accept = false
|
||||
} else {
|
||||
p.ParserData.Read()
|
||||
accept = true
|
||||
}
|
||||
if accept {
|
||||
}
|
||||
}
|
||||
if !accept {
|
||||
if p.LastError < p.ParserData.Pos() {
|
||||
p.LastError = p.ParserData.Pos()
|
||||
}
|
||||
p.ParserData.Seek(save)
|
||||
}
|
||||
}
|
||||
if !accept {
|
||||
p.ParserData.Seek(save)
|
||||
} else {
|
||||
for accept {
|
||||
{
|
||||
save := p.ParserData.Pos()
|
||||
s := p.ParserData.Pos()
|
||||
{
|
||||
accept = true
|
||||
s := p.ParserData.Pos()
|
||||
if p.ParserData.Read() != '?' || p.ParserData.Read() != '>' {
|
||||
p.ParserData.Seek(s)
|
||||
accept = false
|
||||
}
|
||||
}
|
||||
p.ParserData.Seek(s)
|
||||
p.Root.Discard(s)
|
||||
accept = !accept
|
||||
if accept {
|
||||
if p.ParserData.Pos() >= p.ParserData.Len() {
|
||||
accept = false
|
||||
} else {
|
||||
p.ParserData.Read()
|
||||
accept = true
|
||||
}
|
||||
if accept {
|
||||
}
|
||||
}
|
||||
if !accept {
|
||||
if p.LastError < p.ParserData.Pos() {
|
||||
p.LastError = p.ParserData.Pos()
|
||||
}
|
||||
p.ParserData.Seek(save)
|
||||
}
|
||||
}
|
||||
}
|
||||
accept = true
|
||||
}
|
||||
}
|
||||
if accept {
|
||||
{
|
||||
accept = true
|
||||
s := p.ParserData.Pos()
|
||||
if p.ParserData.Read() != '?' || p.ParserData.Read() != '>' {
|
||||
p.ParserData.Seek(s)
|
||||
accept = false
|
||||
}
|
||||
}
|
||||
if accept {
|
||||
{
|
||||
accept = true
|
||||
for accept {
|
||||
accept = p.Spacing()
|
||||
}
|
||||
accept = true
|
||||
}
|
||||
if accept {
|
||||
{
|
||||
accept = true
|
||||
s := p.ParserData.Pos()
|
||||
if p.ParserData.Read() != '<' || p.ParserData.Read() != '!' || p.ParserData.Read() != 'D' || p.ParserData.Read() != 'O' || p.ParserData.Read() != 'C' || p.ParserData.Read() != 'T' || p.ParserData.Read() != 'Y' || p.ParserData.Read() != 'P' || p.ParserData.Read() != 'E' {
|
||||
p.ParserData.Seek(s)
|
||||
accept = false
|
||||
}
|
||||
}
|
||||
if accept {
|
||||
{
|
||||
save := p.ParserData.Pos()
|
||||
{
|
||||
save := p.ParserData.Pos()
|
||||
s := p.ParserData.Pos()
|
||||
if p.ParserData.Read() != '>' {
|
||||
p.ParserData.UnRead()
|
||||
accept = false
|
||||
} else {
|
||||
accept = true
|
||||
}
|
||||
p.ParserData.Seek(s)
|
||||
p.Root.Discard(s)
|
||||
accept = !accept
|
||||
if accept {
|
||||
if p.ParserData.Pos() >= p.ParserData.Len() {
|
||||
accept = false
|
||||
} else {
|
||||
p.ParserData.Read()
|
||||
accept = true
|
||||
}
|
||||
if accept {
|
||||
}
|
||||
}
|
||||
if !accept {
|
||||
if p.LastError < p.ParserData.Pos() {
|
||||
p.LastError = p.ParserData.Pos()
|
||||
}
|
||||
p.ParserData.Seek(save)
|
||||
}
|
||||
}
|
||||
if !accept {
|
||||
p.ParserData.Seek(save)
|
||||
} else {
|
||||
for accept {
|
||||
{
|
||||
save := p.ParserData.Pos()
|
||||
s := p.ParserData.Pos()
|
||||
if p.ParserData.Read() != '>' {
|
||||
p.ParserData.UnRead()
|
||||
accept = false
|
||||
} else {
|
||||
accept = true
|
||||
}
|
||||
p.ParserData.Seek(s)
|
||||
p.Root.Discard(s)
|
||||
accept = !accept
|
||||
if accept {
|
||||
if p.ParserData.Pos() >= p.ParserData.Len() {
|
||||
accept = false
|
||||
} else {
|
||||
p.ParserData.Read()
|
||||
accept = true
|
||||
}
|
||||
if accept {
|
||||
}
|
||||
}
|
||||
if !accept {
|
||||
if p.LastError < p.ParserData.Pos() {
|
||||
p.LastError = p.ParserData.Pos()
|
||||
}
|
||||
p.ParserData.Seek(save)
|
||||
}
|
||||
}
|
||||
}
|
||||
accept = true
|
||||
}
|
||||
}
|
||||
if accept {
|
||||
if p.ParserData.Read() != '>' {
|
||||
p.ParserData.UnRead()
|
||||
accept = false
|
||||
} else {
|
||||
accept = true
|
||||
}
|
||||
if accept {
|
||||
{
|
||||
accept = true
|
||||
for accept {
|
||||
accept = p.Spacing()
|
||||
}
|
||||
accept = true
|
||||
}
|
||||
if accept {
|
||||
accept = p.Plist()
|
||||
if accept {
|
||||
{
|
||||
accept = true
|
||||
for accept {
|
||||
accept = p.Spacing()
|
||||
}
|
||||
accept = true
|
||||
}
|
||||
if accept {
|
||||
accept = p.EndOfFile()
|
||||
accept = true
|
||||
if accept {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !accept {
|
||||
if p.LastError < p.ParserData.Pos() {
|
||||
p.LastError = p.ParserData.Pos()
|
||||
}
|
||||
p.ParserData.Seek(save)
|
||||
}
|
||||
}
|
||||
if accept && start != p.ParserData.Pos() {
|
||||
if start < p.IgnoreRange.A || p.IgnoreRange.A == 0 {
|
||||
p.IgnoreRange.A = start
|
||||
}
|
||||
p.IgnoreRange.B = p.ParserData.Pos()
|
||||
}
|
||||
return accept
|
||||
}
|
||||
|
||||
func (p *PLIST) Plist() bool {
|
||||
// Plist <- "<plist version=\"1.0\">" Values "</plist>"
|
||||
accept := false
|
||||
accept = true
|
||||
start := p.ParserData.Pos()
|
||||
{
|
||||
save := p.ParserData.Pos()
|
||||
{
|
||||
accept = true
|
||||
s := p.ParserData.Pos()
|
||||
if p.ParserData.Read() != '<' || p.ParserData.Read() != 'p' || p.ParserData.Read() != 'l' || p.ParserData.Read() != 'i' || p.ParserData.Read() != 's' || p.ParserData.Read() != 't' || p.ParserData.Read() != ' ' || p.ParserData.Read() != 'v' || p.ParserData.Read() != 'e' || p.ParserData.Read() != 'r' || p.ParserData.Read() != 's' || p.ParserData.Read() != 'i' || p.ParserData.Read() != 'o' || p.ParserData.Read() != 'n' || p.ParserData.Read() != '=' || p.ParserData.Read() != '"' || p.ParserData.Read() != '1' || p.ParserData.Read() != '.' || p.ParserData.Read() != '0' || p.ParserData.Read() != '"' || p.ParserData.Read() != '>' {
|
||||
p.ParserData.Seek(s)
|
||||
accept = false
|
||||
}
|
||||
}
|
||||
if accept {
|
||||
accept = p.Values()
|
||||
if accept {
|
||||
{
|
||||
accept = true
|
||||
s := p.ParserData.Pos()
|
||||
if p.ParserData.Read() != '<' || p.ParserData.Read() != '/' || p.ParserData.Read() != 'p' || p.ParserData.Read() != 'l' || p.ParserData.Read() != 'i' || p.ParserData.Read() != 's' || p.ParserData.Read() != 't' || p.ParserData.Read() != '>' {
|
||||
p.ParserData.Seek(s)
|
||||
accept = false
|
||||
}
|
||||
}
|
||||
if accept {
|
||||
}
|
||||
}
|
||||
}
|
||||
if !accept {
|
||||
if p.LastError < p.ParserData.Pos() {
|
||||
p.LastError = p.ParserData.Pos()
|
||||
}
|
||||
p.ParserData.Seek(save)
|
||||
}
|
||||
}
|
||||
if accept && start != p.ParserData.Pos() {
|
||||
if start < p.IgnoreRange.A || p.IgnoreRange.A == 0 {
|
||||
p.IgnoreRange.A = start
|
||||
}
|
||||
p.IgnoreRange.B = p.ParserData.Pos()
|
||||
}
|
||||
return accept
|
||||
}
|
||||
|
||||
func (p *PLIST) Dictionary() bool {
|
||||
// Dictionary <- "<dict>" KeyValuePair+ "</dict>" / "<dict/>"
|
||||
accept := false
|
||||
accept = true
|
||||
start := p.ParserData.Pos()
|
||||
{
|
||||
save := p.ParserData.Pos()
|
||||
{
|
||||
save := p.ParserData.Pos()
|
||||
{
|
||||
accept = true
|
||||
s := p.ParserData.Pos()
|
||||
if p.ParserData.Read() != '<' || p.ParserData.Read() != 'd' || p.ParserData.Read() != 'i' || p.ParserData.Read() != 'c' || p.ParserData.Read() != 't' || p.ParserData.Read() != '>' {
|
||||
p.ParserData.Seek(s)
|
||||
accept = false
|
||||
}
|
||||
}
|
||||
if accept {
|
||||
{
|
||||
save := p.ParserData.Pos()
|
||||
accept = p.KeyValuePair()
|
||||
if !accept {
|
||||
p.ParserData.Seek(save)
|
||||
} else {
|
||||
for accept {
|
||||
accept = p.KeyValuePair()
|
||||
}
|
||||
accept = true
|
||||
}
|
||||
}
|
||||
if accept {
|
||||
{
|
||||
accept = true
|
||||
s := p.ParserData.Pos()
|
||||
if p.ParserData.Read() != '<' || p.ParserData.Read() != '/' || p.ParserData.Read() != 'd' || p.ParserData.Read() != 'i' || p.ParserData.Read() != 'c' || p.ParserData.Read() != 't' || p.ParserData.Read() != '>' {
|
||||
p.ParserData.Seek(s)
|
||||
accept = false
|
||||
}
|
||||
}
|
||||
if accept {
|
||||
}
|
||||
}
|
||||
}
|
||||
if !accept {
|
||||
if p.LastError < p.ParserData.Pos() {
|
||||
p.LastError = p.ParserData.Pos()
|
||||
}
|
||||
p.ParserData.Seek(save)
|
||||
}
|
||||
}
|
||||
if !accept {
|
||||
{
|
||||
accept = true
|
||||
s := p.ParserData.Pos()
|
||||
if p.ParserData.Read() != '<' || p.ParserData.Read() != 'd' || p.ParserData.Read() != 'i' || p.ParserData.Read() != 'c' || p.ParserData.Read() != 't' || p.ParserData.Read() != '/' || p.ParserData.Read() != '>' {
|
||||
p.ParserData.Seek(s)
|
||||
accept = false
|
||||
}
|
||||
}
|
||||
if !accept {
|
||||
}
|
||||
}
|
||||
if !accept {
|
||||
p.ParserData.Seek(save)
|
||||
}
|
||||
}
|
||||
end := p.ParserData.Pos()
|
||||
if accept {
|
||||
node := p.Root.Cleanup(start, end)
|
||||
node.Name = "Dictionary"
|
||||
node.P = p
|
||||
node.Range = node.Range.Clip(p.IgnoreRange)
|
||||
p.Root.Append(node)
|
||||
} else {
|
||||
p.Root.Discard(start)
|
||||
}
|
||||
if p.IgnoreRange.A >= end || p.IgnoreRange.B <= start {
|
||||
p.IgnoreRange = text.Region{}
|
||||
}
|
||||
return accept
|
||||
}
|
||||
|
||||
func (p *PLIST) KeyValuePair() bool {
|
||||
// KeyValuePair <- Spacing* KeyTag Spacing* Value Spacing*
|
||||
accept := false
|
||||
accept = true
|
||||
start := p.ParserData.Pos()
|
||||
{
|
||||
save := p.ParserData.Pos()
|
||||
{
|
||||
accept = true
|
||||
for accept {
|
||||
accept = p.Spacing()
|
||||
}
|
||||
accept = true
|
||||
}
|
||||
if accept {
|
||||
accept = p.KeyTag()
|
||||
if accept {
|
||||
{
|
||||
accept = true
|
||||
for accept {
|
||||
accept = p.Spacing()
|
||||
}
|
||||
accept = true
|
||||
}
|
||||
if accept {
|
||||
accept = p.Value()
|
||||
if accept {
|
||||
{
|
||||
accept = true
|
||||
for accept {
|
||||
accept = p.Spacing()
|
||||
}
|
||||
accept = true
|
||||
}
|
||||
if accept {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !accept {
|
||||
if p.LastError < p.ParserData.Pos() {
|
||||
p.LastError = p.ParserData.Pos()
|
||||
}
|
||||
p.ParserData.Seek(save)
|
||||
}
|
||||
}
|
||||
if accept && start != p.ParserData.Pos() {
|
||||
if start < p.IgnoreRange.A || p.IgnoreRange.A == 0 {
|
||||
p.IgnoreRange.A = start
|
||||
}
|
||||
p.IgnoreRange.B = p.ParserData.Pos()
|
||||
}
|
||||
return accept
|
||||
}
|
||||
|
||||
func (p *PLIST) KeyTag() bool {
|
||||
// KeyTag <- "<key>" Key "</key>"
|
||||
accept := false
|
||||
accept = true
|
||||
start := p.ParserData.Pos()
|
||||
{
|
||||
save := p.ParserData.Pos()
|
||||
{
|
||||
accept = true
|
||||
s := p.ParserData.Pos()
|
||||
if p.ParserData.Read() != '<' || p.ParserData.Read() != 'k' || p.ParserData.Read() != 'e' || p.ParserData.Read() != 'y' || p.ParserData.Read() != '>' {
|
||||
p.ParserData.Seek(s)
|
||||
accept = false
|
||||
}
|
||||
}
|
||||
if accept {
|
||||
accept = p.Key()
|
||||
if accept {
|
||||
{
|
||||
accept = true
|
||||
s := p.ParserData.Pos()
|
||||
if p.ParserData.Read() != '<' || p.ParserData.Read() != '/' || p.ParserData.Read() != 'k' || p.ParserData.Read() != 'e' || p.ParserData.Read() != 'y' || p.ParserData.Read() != '>' {
|
||||
p.ParserData.Seek(s)
|
||||
accept = false
|
||||
}
|
||||
}
|
||||
if accept {
|
||||
}
|
||||
}
|
||||
}
|
||||
if !accept {
|
||||
if p.LastError < p.ParserData.Pos() {
|
||||
p.LastError = p.ParserData.Pos()
|
||||
}
|
||||
p.ParserData.Seek(save)
|
||||
}
|
||||
}
|
||||
if accept && start != p.ParserData.Pos() {
|
||||
if start < p.IgnoreRange.A || p.IgnoreRange.A == 0 {
|
||||
p.IgnoreRange.A = start
|
||||
}
|
||||
p.IgnoreRange.B = p.ParserData.Pos()
|
||||
}
|
||||
return accept
|
||||
}
|
||||
|
||||
func (p *PLIST) Key() bool {
|
||||
// Key <- (!'<' .)*
|
||||
accept := false
|
||||
accept = true
|
||||
start := p.ParserData.Pos()
|
||||
{
|
||||
accept = true
|
||||
for accept {
|
||||
{
|
||||
save := p.ParserData.Pos()
|
||||
s := p.ParserData.Pos()
|
||||
if p.ParserData.Read() != '<' {
|
||||
p.ParserData.UnRead()
|
||||
accept = false
|
||||
} else {
|
||||
accept = true
|
||||
}
|
||||
p.ParserData.Seek(s)
|
||||
p.Root.Discard(s)
|
||||
accept = !accept
|
||||
if accept {
|
||||
if p.ParserData.Pos() >= p.ParserData.Len() {
|
||||
accept = false
|
||||
} else {
|
||||
p.ParserData.Read()
|
||||
accept = true
|
||||
}
|
||||
if accept {
|
||||
}
|
||||
}
|
||||
if !accept {
|
||||
if p.LastError < p.ParserData.Pos() {
|
||||
p.LastError = p.ParserData.Pos()
|
||||
}
|
||||
p.ParserData.Seek(save)
|
||||
}
|
||||
}
|
||||
}
|
||||
accept = true
|
||||
}
|
||||
end := p.ParserData.Pos()
|
||||
if accept {
|
||||
node := p.Root.Cleanup(start, end)
|
||||
node.Name = "Key"
|
||||
node.P = p
|
||||
node.Range = node.Range.Clip(p.IgnoreRange)
|
||||
p.Root.Append(node)
|
||||
} else {
|
||||
p.Root.Discard(start)
|
||||
}
|
||||
if p.IgnoreRange.A >= end || p.IgnoreRange.B <= start {
|
||||
p.IgnoreRange = text.Region{}
|
||||
}
|
||||
return accept
|
||||
}
|
||||
|
||||
func (p *PLIST) StringTag() bool {
|
||||
// StringTag <- "<string>" String "</string>"
|
||||
accept := false
|
||||
accept = true
|
||||
start := p.ParserData.Pos()
|
||||
{
|
||||
save := p.ParserData.Pos()
|
||||
{
|
||||
accept = true
|
||||
s := p.ParserData.Pos()
|
||||
if p.ParserData.Read() != '<' || p.ParserData.Read() != 's' || p.ParserData.Read() != 't' || p.ParserData.Read() != 'r' || p.ParserData.Read() != 'i' || p.ParserData.Read() != 'n' || p.ParserData.Read() != 'g' || p.ParserData.Read() != '>' {
|
||||
p.ParserData.Seek(s)
|
||||
accept = false
|
||||
}
|
||||
}
|
||||
if accept {
|
||||
accept = p.String()
|
||||
if accept {
|
||||
{
|
||||
accept = true
|
||||
s := p.ParserData.Pos()
|
||||
if p.ParserData.Read() != '<' || p.ParserData.Read() != '/' || p.ParserData.Read() != 's' || p.ParserData.Read() != 't' || p.ParserData.Read() != 'r' || p.ParserData.Read() != 'i' || p.ParserData.Read() != 'n' || p.ParserData.Read() != 'g' || p.ParserData.Read() != '>' {
|
||||
p.ParserData.Seek(s)
|
||||
accept = false
|
||||
}
|
||||
}
|
||||
if accept {
|
||||
}
|
||||
}
|
||||
}
|
||||
if !accept {
|
||||
if p.LastError < p.ParserData.Pos() {
|
||||
p.LastError = p.ParserData.Pos()
|
||||
}
|
||||
p.ParserData.Seek(save)
|
||||
}
|
||||
}
|
||||
if accept && start != p.ParserData.Pos() {
|
||||
if start < p.IgnoreRange.A || p.IgnoreRange.A == 0 {
|
||||
p.IgnoreRange.A = start
|
||||
}
|
||||
p.IgnoreRange.B = p.ParserData.Pos()
|
||||
}
|
||||
return accept
|
||||
}
|
||||
|
||||
func (p *PLIST) String() bool {
|
||||
// String <- (!'<' .)*
|
||||
accept := false
|
||||
accept = true
|
||||
start := p.ParserData.Pos()
|
||||
{
|
||||
accept = true
|
||||
for accept {
|
||||
{
|
||||
save := p.ParserData.Pos()
|
||||
s := p.ParserData.Pos()
|
||||
if p.ParserData.Read() != '<' {
|
||||
p.ParserData.UnRead()
|
||||
accept = false
|
||||
} else {
|
||||
accept = true
|
||||
}
|
||||
p.ParserData.Seek(s)
|
||||
p.Root.Discard(s)
|
||||
accept = !accept
|
||||
if accept {
|
||||
if p.ParserData.Pos() >= p.ParserData.Len() {
|
||||
accept = false
|
||||
} else {
|
||||
p.ParserData.Read()
|
||||
accept = true
|
||||
}
|
||||
if accept {
|
||||
}
|
||||
}
|
||||
if !accept {
|
||||
if p.LastError < p.ParserData.Pos() {
|
||||
p.LastError = p.ParserData.Pos()
|
||||
}
|
||||
p.ParserData.Seek(save)
|
||||
}
|
||||
}
|
||||
}
|
||||
accept = true
|
||||
}
|
||||
end := p.ParserData.Pos()
|
||||
if accept {
|
||||
node := p.Root.Cleanup(start, end)
|
||||
node.Name = "String"
|
||||
node.P = p
|
||||
node.Range = node.Range.Clip(p.IgnoreRange)
|
||||
p.Root.Append(node)
|
||||
} else {
|
||||
p.Root.Discard(start)
|
||||
}
|
||||
if p.IgnoreRange.A >= end || p.IgnoreRange.B <= start {
|
||||
p.IgnoreRange = text.Region{}
|
||||
}
|
||||
return accept
|
||||
}
|
||||
|
||||
func (p *PLIST) Value() bool {
|
||||
// Value <- Array / StringTag / Dictionary
|
||||
accept := false
|
||||
accept = true
|
||||
start := p.ParserData.Pos()
|
||||
{
|
||||
save := p.ParserData.Pos()
|
||||
accept = p.Array()
|
||||
if !accept {
|
||||
accept = p.StringTag()
|
||||
if !accept {
|
||||
accept = p.Dictionary()
|
||||
if !accept {
|
||||
}
|
||||
}
|
||||
}
|
||||
if !accept {
|
||||
p.ParserData.Seek(save)
|
||||
}
|
||||
}
|
||||
if accept && start != p.ParserData.Pos() {
|
||||
if start < p.IgnoreRange.A || p.IgnoreRange.A == 0 {
|
||||
p.IgnoreRange.A = start
|
||||
}
|
||||
p.IgnoreRange.B = p.ParserData.Pos()
|
||||
}
|
||||
return accept
|
||||
}
|
||||
|
||||
func (p *PLIST) Values() bool {
|
||||
// Values <- (Spacing* Value Spacing*)*
|
||||
accept := false
|
||||
accept = true
|
||||
start := p.ParserData.Pos()
|
||||
{
|
||||
accept = true
|
||||
for accept {
|
||||
{
|
||||
save := p.ParserData.Pos()
|
||||
{
|
||||
accept = true
|
||||
for accept {
|
||||
accept = p.Spacing()
|
||||
}
|
||||
accept = true
|
||||
}
|
||||
if accept {
|
||||
accept = p.Value()
|
||||
if accept {
|
||||
{
|
||||
accept = true
|
||||
for accept {
|
||||
accept = p.Spacing()
|
||||
}
|
||||
accept = true
|
||||
}
|
||||
if accept {
|
||||
}
|
||||
}
|
||||
}
|
||||
if !accept {
|
||||
if p.LastError < p.ParserData.Pos() {
|
||||
p.LastError = p.ParserData.Pos()
|
||||
}
|
||||
p.ParserData.Seek(save)
|
||||
}
|
||||
}
|
||||
}
|
||||
accept = true
|
||||
}
|
||||
if accept && start != p.ParserData.Pos() {
|
||||
if start < p.IgnoreRange.A || p.IgnoreRange.A == 0 {
|
||||
p.IgnoreRange.A = start
|
||||
}
|
||||
p.IgnoreRange.B = p.ParserData.Pos()
|
||||
}
|
||||
return accept
|
||||
}
|
||||
|
||||
func (p *PLIST) Array() bool {
|
||||
// Array <- "<array>" Values "</array>" / "<array/>"
|
||||
accept := false
|
||||
accept = true
|
||||
start := p.ParserData.Pos()
|
||||
{
|
||||
save := p.ParserData.Pos()
|
||||
{
|
||||
save := p.ParserData.Pos()
|
||||
{
|
||||
accept = true
|
||||
s := p.ParserData.Pos()
|
||||
if p.ParserData.Read() != '<' || p.ParserData.Read() != 'a' || p.ParserData.Read() != 'r' || p.ParserData.Read() != 'r' || p.ParserData.Read() != 'a' || p.ParserData.Read() != 'y' || p.ParserData.Read() != '>' {
|
||||
p.ParserData.Seek(s)
|
||||
accept = false
|
||||
}
|
||||
}
|
||||
if accept {
|
||||
accept = p.Values()
|
||||
if accept {
|
||||
{
|
||||
accept = true
|
||||
s := p.ParserData.Pos()
|
||||
if p.ParserData.Read() != '<' || p.ParserData.Read() != '/' || p.ParserData.Read() != 'a' || p.ParserData.Read() != 'r' || p.ParserData.Read() != 'r' || p.ParserData.Read() != 'a' || p.ParserData.Read() != 'y' || p.ParserData.Read() != '>' {
|
||||
p.ParserData.Seek(s)
|
||||
accept = false
|
||||
}
|
||||
}
|
||||
if accept {
|
||||
}
|
||||
}
|
||||
}
|
||||
if !accept {
|
||||
if p.LastError < p.ParserData.Pos() {
|
||||
p.LastError = p.ParserData.Pos()
|
||||
}
|
||||
p.ParserData.Seek(save)
|
||||
}
|
||||
}
|
||||
if !accept {
|
||||
{
|
||||
accept = true
|
||||
s := p.ParserData.Pos()
|
||||
if p.ParserData.Read() != '<' || p.ParserData.Read() != 'a' || p.ParserData.Read() != 'r' || p.ParserData.Read() != 'r' || p.ParserData.Read() != 'a' || p.ParserData.Read() != 'y' || p.ParserData.Read() != '/' || p.ParserData.Read() != '>' {
|
||||
p.ParserData.Seek(s)
|
||||
accept = false
|
||||
}
|
||||
}
|
||||
if !accept {
|
||||
}
|
||||
}
|
||||
if !accept {
|
||||
p.ParserData.Seek(save)
|
||||
}
|
||||
}
|
||||
end := p.ParserData.Pos()
|
||||
if accept {
|
||||
node := p.Root.Cleanup(start, end)
|
||||
node.Name = "Array"
|
||||
node.P = p
|
||||
node.Range = node.Range.Clip(p.IgnoreRange)
|
||||
p.Root.Append(node)
|
||||
} else {
|
||||
p.Root.Discard(start)
|
||||
}
|
||||
if p.IgnoreRange.A >= end || p.IgnoreRange.B <= start {
|
||||
p.IgnoreRange = text.Region{}
|
||||
}
|
||||
return accept
|
||||
}
|
||||
|
||||
func (p *PLIST) Spacing() bool {
|
||||
// Spacing <- [ \t\n\r]+
|
||||
accept := false
|
||||
accept = true
|
||||
start := p.ParserData.Pos()
|
||||
{
|
||||
save := p.ParserData.Pos()
|
||||
{
|
||||
accept = false
|
||||
c := p.ParserData.Read()
|
||||
if c == ' ' || c == '\t' || c == '\n' || c == '\r' {
|
||||
accept = true
|
||||
} else {
|
||||
p.ParserData.UnRead()
|
||||
}
|
||||
}
|
||||
if !accept {
|
||||
p.ParserData.Seek(save)
|
||||
} else {
|
||||
for accept {
|
||||
{
|
||||
accept = false
|
||||
c := p.ParserData.Read()
|
||||
if c == ' ' || c == '\t' || c == '\n' || c == '\r' {
|
||||
accept = true
|
||||
} else {
|
||||
p.ParserData.UnRead()
|
||||
}
|
||||
}
|
||||
}
|
||||
accept = true
|
||||
}
|
||||
}
|
||||
if accept && start != p.ParserData.Pos() {
|
||||
if start < p.IgnoreRange.A || p.IgnoreRange.A == 0 {
|
||||
p.IgnoreRange.A = start
|
||||
}
|
||||
p.IgnoreRange.B = p.ParserData.Pos()
|
||||
}
|
||||
return accept
|
||||
}
|
||||
|
||||
func (p *PLIST) EndOfFile() bool {
|
||||
// EndOfFile <- !.
|
||||
accept := false
|
||||
accept = true
|
||||
start := p.ParserData.Pos()
|
||||
s := p.ParserData.Pos()
|
||||
if p.ParserData.Pos() >= p.ParserData.Len() {
|
||||
accept = false
|
||||
} else {
|
||||
p.ParserData.Read()
|
||||
accept = true
|
||||
}
|
||||
p.ParserData.Seek(s)
|
||||
p.Root.Discard(s)
|
||||
accept = !accept
|
||||
end := p.ParserData.Pos()
|
||||
if accept {
|
||||
node := p.Root.Cleanup(start, end)
|
||||
node.Name = "EndOfFile"
|
||||
node.P = p
|
||||
node.Range = node.Range.Clip(p.IgnoreRange)
|
||||
p.Root.Append(node)
|
||||
} else {
|
||||
p.Root.Discard(start)
|
||||
}
|
||||
if p.IgnoreRange.A >= end || p.IgnoreRange.B <= start {
|
||||
p.IgnoreRange = text.Region{}
|
||||
}
|
||||
return accept
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
// Copyright 2015 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package plist
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ = time.Time{}
|
||||
|
||||
const testname = "testdata/C.plist"
|
||||
|
||||
func loadData(path string) (retdata string, err error) {
|
||||
var data []byte
|
||||
if strings.HasPrefix(path, "http://") {
|
||||
p2 := strings.Replace(strings.Replace(path, "http://", "http_", -1), "/", "_", -1)
|
||||
if retdata, err = loadData(p2); err != nil {
|
||||
if res, err := http.Get(path); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
defer res.Body.Close()
|
||||
if data, err = ioutil.ReadAll(res.Body); err != nil {
|
||||
return "", err
|
||||
} else if err = ioutil.WriteFile(p2, data, 0644); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return loadData(p2)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} else if strings.HasSuffix(path, ".zip") {
|
||||
if zf, err := zip.OpenReader(path); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
defer zf.Close()
|
||||
f, err := zf.File[0].Open()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
if data, err = ioutil.ReadAll(f); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data, err = ioutil.ReadFile(path)
|
||||
}
|
||||
return string(data), err
|
||||
}
|
||||
|
||||
func TestParser(t *testing.T) {
|
||||
var p PLIST
|
||||
if data, err := loadData(testname); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
root := p.RootNode()
|
||||
if !p.Parse(data) {
|
||||
|
||||
t.Fatalf("Didn't parse correctly: %s\n", p.Error())
|
||||
} else {
|
||||
|
||||
if root.Range.B != p.ParserData.Len() {
|
||||
t.Fatalf("Parsing didn't finish: %v\n%s", root, p.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParser(b *testing.B) {
|
||||
var p PLIST
|
||||
if data, err := loadData(testname); err != nil {
|
||||
b.Fatal(err)
|
||||
} else {
|
||||
for i := 0; i < b.N; i++ {
|
||||
p.Parse(data)
|
||||
}
|
||||
}
|
||||
}
|
1202
backend/loaders/plist/testdata/C.plist
vendored
1202
backend/loaders/plist/testdata/C.plist
vendored
File diff suppressed because it is too large
Load Diff
@ -1,33 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package log
|
||||
|
||||
import (
|
||||
"github.com/limetext/log4go"
|
||||
)
|
||||
|
||||
type (
|
||||
consoleLogWriter struct {
|
||||
logWriter
|
||||
writer log4go.ConsoleLogWriter
|
||||
}
|
||||
)
|
||||
|
||||
func NewConsoleLogWriter() *consoleLogWriter {
|
||||
ret := &consoleLogWriter{
|
||||
writer: log4go.NewConsoleLogWriter(),
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Implement LogWriter
|
||||
|
||||
func (l *consoleLogWriter) LogWrite(rec *log4go.LogRecord) {
|
||||
l.writer.LogWrite(rec)
|
||||
}
|
||||
|
||||
func (l *consoleLogWriter) Close() {
|
||||
l.writer.Close()
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package log
|
||||
|
||||
import (
|
||||
"github.com/limetext/log4go"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewConsoleLogWriter(t *testing.T) {
|
||||
l := NewConsoleLogWriter()
|
||||
if l == nil {
|
||||
t.Error("NewConsoleLogWriter produced a nil")
|
||||
}
|
||||
l.Close()
|
||||
}
|
||||
|
||||
func TestConsoleLogWriterLogWrite(t *testing.T) {
|
||||
l := NewConsoleLogWriter()
|
||||
logRecord := &log4go.LogRecord{}
|
||||
l.LogWrite(logRecord) // Void function. Testing for coverage.
|
||||
l.Close()
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package log
|
||||
|
||||
import (
|
||||
"github.com/limetext/log4go"
|
||||
)
|
||||
|
||||
type (
|
||||
fileLogWriter struct {
|
||||
logWriter
|
||||
writer *log4go.FileLogWriter
|
||||
}
|
||||
)
|
||||
|
||||
func NewFileLogWriter(fname string, rotate bool) *fileLogWriter {
|
||||
ret := &fileLogWriter{
|
||||
writer: log4go.NewFileLogWriter(fname, rotate),
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Implement LogWriter
|
||||
|
||||
func (l *fileLogWriter) LogWrite(rec *log4go.LogRecord) {
|
||||
l.writer.LogWrite(rec)
|
||||
}
|
||||
|
||||
func (l *fileLogWriter) Close() {
|
||||
l.writer.Close()
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package log
|
||||
|
||||
import (
|
||||
"github.com/limetext/log4go"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const logfn = "some file"
|
||||
|
||||
func TestNewFileLogWriter(t *testing.T) {
|
||||
l := NewFileLogWriter(logfn, true)
|
||||
defer os.Remove(logfn)
|
||||
if l == nil {
|
||||
t.Error("NewFileLogWriter produced a nil")
|
||||
}
|
||||
l.Close()
|
||||
}
|
||||
|
||||
func TestFileLogWriterLogWrite(t *testing.T) {
|
||||
l := NewFileLogWriter(logfn, true)
|
||||
defer os.Remove(logfn)
|
||||
logRecord := &log4go.LogRecord{}
|
||||
l.LogWrite(logRecord) // Void function. Testing for coverage.
|
||||
l.Close()
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package log
|
||||
|
||||
import (
|
||||
"github.com/limetext/log4go"
|
||||
)
|
||||
|
||||
var (
|
||||
Global *Logger
|
||||
)
|
||||
|
||||
func init() {
|
||||
log4go.Global.Close()
|
||||
Global = &Logger{
|
||||
logger: log4go.Global,
|
||||
}
|
||||
}
|
||||
|
||||
func AddFilter(name string, level Level, writer LogWriter) {
|
||||
Global.AddFilter(name, level, writer)
|
||||
}
|
||||
|
||||
func Finest(arg0 interface{}, args ...interface{}) {
|
||||
Global.Finest(arg0, args...)
|
||||
}
|
||||
|
||||
func Fine(arg0 interface{}, args ...interface{}) {
|
||||
Global.Fine(arg0, args...)
|
||||
}
|
||||
|
||||
func Debug(arg0 interface{}, args ...interface{}) {
|
||||
Global.Debug(arg0, args...)
|
||||
}
|
||||
|
||||
func Trace(arg0 interface{}, args ...interface{}) {
|
||||
Global.Trace(arg0, args...)
|
||||
}
|
||||
|
||||
func Info(arg0 interface{}, args ...interface{}) {
|
||||
Global.Info(arg0, args...)
|
||||
}
|
||||
|
||||
func Warn(arg0 interface{}, args ...interface{}) {
|
||||
Global.Warn(arg0, args...)
|
||||
}
|
||||
|
||||
func Error(arg0 interface{}, args ...interface{}) {
|
||||
Global.Error(arg0, args...)
|
||||
}
|
||||
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
Global.Errorf(format, args...)
|
||||
}
|
||||
|
||||
func Critical(arg0 interface{}, args ...interface{}) {
|
||||
Global.Critical(arg0, args...)
|
||||
}
|
||||
|
||||
func Logf(level Level, format string, args ...interface{}) {
|
||||
Global.Logf(level, format, args...)
|
||||
}
|
||||
|
||||
func Close(args ...interface{}) {
|
||||
Global.Close(args...)
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package log
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGlobalFunctions(t *testing.T) {
|
||||
AddFilter("add filter", FINE, testlogger(func(str string) {}))
|
||||
Finest("testing finest")
|
||||
Fine("testing fine")
|
||||
Debug("testing debug")
|
||||
Trace("testing trace")
|
||||
Warn("testing warn")
|
||||
Error("testing error")
|
||||
Critical("testing critical")
|
||||
Logf(FINE, "testing logf")
|
||||
Close("testing close")
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package log
|
||||
|
||||
type (
|
||||
Level int
|
||||
)
|
||||
|
||||
const (
|
||||
FINEST Level = iota
|
||||
FINE
|
||||
DEBUG
|
||||
TRACE
|
||||
INFO
|
||||
WARNING
|
||||
ERROR
|
||||
CRITICAL
|
||||
)
|
@ -1,113 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package log
|
||||
|
||||
import (
|
||||
"github.com/limetext/log4go"
|
||||
)
|
||||
|
||||
type (
|
||||
Logger struct {
|
||||
logger log4go.Logger
|
||||
}
|
||||
)
|
||||
|
||||
func NewLogger() *Logger {
|
||||
l := &Logger{
|
||||
logger: make(log4go.Logger),
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *Logger) AddFilter(name string, level Level, writer LogWriter) {
|
||||
lvl := log4go.INFO
|
||||
switch level {
|
||||
case FINEST:
|
||||
lvl = log4go.FINEST
|
||||
case FINE:
|
||||
lvl = log4go.FINE
|
||||
case DEBUG:
|
||||
lvl = log4go.DEBUG
|
||||
case TRACE:
|
||||
lvl = log4go.TRACE
|
||||
case INFO:
|
||||
lvl = log4go.INFO
|
||||
case WARNING:
|
||||
lvl = log4go.WARNING
|
||||
case ERROR:
|
||||
lvl = log4go.ERROR
|
||||
case CRITICAL:
|
||||
lvl = log4go.CRITICAL
|
||||
default:
|
||||
}
|
||||
l.logger.AddFilter(name, lvl, writer)
|
||||
}
|
||||
|
||||
func (l *Logger) Finest(arg0 interface{}, args ...interface{}) {
|
||||
l.logger.Finest(arg0, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Fine(arg0 interface{}, args ...interface{}) {
|
||||
l.logger.Fine(arg0, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Debug(arg0 interface{}, args ...interface{}) {
|
||||
l.logger.Debug(arg0, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Trace(arg0 interface{}, args ...interface{}) {
|
||||
l.logger.Trace(arg0, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Info(arg0 interface{}, args ...interface{}) {
|
||||
l.logger.Info(arg0, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Warn(arg0 interface{}, args ...interface{}) {
|
||||
l.logger.Warn(arg0, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Error(arg0 interface{}, args ...interface{}) {
|
||||
l.logger.Error(arg0, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Errorf(format string, args ...interface{}) {
|
||||
l.Logf(ERROR, format, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Critical(arg0 interface{}, args ...interface{}) {
|
||||
l.logger.Critical(arg0, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Logf(level Level, format string, args ...interface{}) {
|
||||
lvl := log4go.INFO
|
||||
switch level {
|
||||
case FINEST:
|
||||
lvl = log4go.FINEST
|
||||
case FINE:
|
||||
lvl = log4go.FINE
|
||||
case DEBUG:
|
||||
lvl = log4go.DEBUG
|
||||
case TRACE:
|
||||
lvl = log4go.TRACE
|
||||
case INFO:
|
||||
lvl = log4go.INFO
|
||||
case WARNING:
|
||||
lvl = log4go.WARNING
|
||||
case ERROR:
|
||||
lvl = log4go.ERROR
|
||||
case CRITICAL:
|
||||
lvl = log4go.CRITICAL
|
||||
default:
|
||||
}
|
||||
l.logger.Logf(lvl, format, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Close(args ...interface{}) {
|
||||
if len(args) > 0 {
|
||||
l.Error(args)
|
||||
}
|
||||
l.logger.Close()
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package log
|
||||
|
||||
import (
|
||||
"github.com/limetext/log4go"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type testlogger func(string)
|
||||
|
||||
func (l testlogger) LogWrite(rec *log4go.LogRecord) {
|
||||
l(rec.Message)
|
||||
}
|
||||
|
||||
func (l testlogger) Close() {}
|
||||
|
||||
func TestGlobalLog(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
Global.Close()
|
||||
Global.AddFilter("globaltest", FINEST, testlogger(func(str string) {
|
||||
if str != "Testing: hello world" {
|
||||
t.Errorf("got: %s", str)
|
||||
}
|
||||
wg.Done()
|
||||
}))
|
||||
wg.Add(1)
|
||||
Info("Testing: %s %s", "hello", "world")
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestLogf(t *testing.T) {
|
||||
l := NewLogger()
|
||||
|
||||
// Log a message at each level. Because we cannot access the internals of the logger,
|
||||
// we assume that this test succeeds if it does not cause an error (although we cannot
|
||||
// actually look inside and see if the level was changed)
|
||||
for _, test_lvl := range []Level{FINEST, FINE, DEBUG, TRACE, INFO, WARNING, ERROR, CRITICAL, 999} {
|
||||
l.Logf(test_lvl, time.Now().String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestClose(t *testing.T) {
|
||||
l := NewLogger()
|
||||
l.Close()
|
||||
m := NewLogger()
|
||||
m.Close("something wrong")
|
||||
}
|
||||
|
||||
func TestNewLogger(t *testing.T) {
|
||||
l := NewLogger()
|
||||
if l == nil {
|
||||
t.Error("Returned a nil logger")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogLevels(t *testing.T) {
|
||||
l := NewLogger()
|
||||
|
||||
// Again, because we cannot access the internals of log this will
|
||||
// succeed as long there is no error
|
||||
for _, test_lvl := range []Level{FINEST, FINE, DEBUG, TRACE, INFO, WARNING, ERROR, CRITICAL, 999} {
|
||||
// Use a random-ish string (the current time)
|
||||
l.AddFilter(time.Now().String(), test_lvl, testlogger(func(str string) {}))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogFunctions(t *testing.T) {
|
||||
l := NewLogger()
|
||||
|
||||
l.Finest(time.Now().String())
|
||||
l.Fine(time.Now().String())
|
||||
l.Debug(time.Now().String())
|
||||
l.Trace(time.Now().String())
|
||||
l.Warn(time.Now().String())
|
||||
l.Error(time.Now().String())
|
||||
l.Errorf("%v", time.Now().String())
|
||||
l.Critical(time.Now().String())
|
||||
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package log
|
||||
|
||||
import (
|
||||
. "github.com/limetext/lime/backend/util"
|
||||
"github.com/limetext/log4go"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type (
|
||||
LogWriter interface {
|
||||
log4go.LogWriter
|
||||
}
|
||||
|
||||
logWriter struct {
|
||||
LogWriter
|
||||
log chan string
|
||||
handler func(string)
|
||||
lock sync.Mutex
|
||||
}
|
||||
)
|
||||
|
||||
func NewLogWriter(h func(string)) *logWriter {
|
||||
ret := &logWriter{
|
||||
log: make(chan string, 100),
|
||||
handler: h,
|
||||
}
|
||||
go ret.handle()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (l *logWriter) handle() {
|
||||
for fl := range l.log {
|
||||
l.handler(fl)
|
||||
}
|
||||
}
|
||||
|
||||
// Implement LogWriter
|
||||
|
||||
func (l *logWriter) LogWrite(rec *log4go.LogRecord) {
|
||||
p := Prof.Enter("log")
|
||||
defer p.Exit()
|
||||
l.lock.Lock()
|
||||
defer l.lock.Unlock()
|
||||
fl := log4go.FormatLogRecord(log4go.FORMAT_DEFAULT, rec)
|
||||
l.log <- fl
|
||||
}
|
||||
|
||||
func (l *logWriter) Close() {
|
||||
l.lock.Lock()
|
||||
defer l.lock.Unlock()
|
||||
close(l.log)
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/limetext/log4go"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewLogWriter(t *testing.T) {
|
||||
l := NewLogWriter(func(str string) {})
|
||||
if l == nil {
|
||||
t.Error("NewLogWriter produced a nil")
|
||||
}
|
||||
l.Close()
|
||||
}
|
||||
|
||||
func TestLogWrite(t *testing.T) {
|
||||
l := NewLogWriter(func(str string) { fmt.Print(str) })
|
||||
logRecord := &log4go.LogRecord{}
|
||||
l.LogWrite(logRecord) // Void function. Testing for coverage.
|
||||
l.Close()
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package packages
|
||||
|
||||
type (
|
||||
Package interface {
|
||||
// Returns the path of the package
|
||||
Name() string
|
||||
|
||||
// Depending on the implemented package
|
||||
// returns useful data for python plugin is
|
||||
// python files for setting is file content
|
||||
Get() interface{}
|
||||
|
||||
// Reloads package data
|
||||
Reload()
|
||||
}
|
||||
)
|
||||
|
||||
// This is useful when we are loading new plugin or
|
||||
// scanning for user settings, snippets and etc we
|
||||
// will add files which their suffix contains one of
|
||||
// these keywords
|
||||
var types = []string{"settings", "keymap"}
|
@ -1,98 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/limetext/lime/backend/loaders"
|
||||
"github.com/limetext/lime/backend/log"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
// Packets are small Packages containing 1 file.
|
||||
// Individual settings, keymaps, snippets, etc. are Packets
|
||||
Packet struct {
|
||||
path string
|
||||
// the Packet content will be Unmarshal to this variable
|
||||
// so on reload we know where we should unmarshal it again
|
||||
marshalTo json.Unmarshaler
|
||||
}
|
||||
|
||||
// Useful for managing packets for plugins
|
||||
// and loading user packets for editor
|
||||
Packets []*Packet
|
||||
)
|
||||
|
||||
// Initializes new packet with specific path
|
||||
func NewPacket(path string, marshal json.Unmarshaler) *Packet {
|
||||
return &Packet{path, marshal}
|
||||
}
|
||||
|
||||
func (p *Packet) Name() string {
|
||||
return p.path
|
||||
}
|
||||
|
||||
// Returns packet file data if any error occurred
|
||||
// on reading file we will return nil
|
||||
func (p *Packet) Get() interface{} {
|
||||
e := []byte(`{}`)
|
||||
if p.group() == "keymap" {
|
||||
e = []byte(`[]`)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(p.path); os.IsNotExist(err) {
|
||||
log.Finest("%s doesn't exist yet", p.path)
|
||||
return e
|
||||
}
|
||||
|
||||
d, err := ioutil.ReadFile(p.path)
|
||||
if err != nil {
|
||||
log.Errorf("Couldn't read file: %s", err)
|
||||
return e
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// Forces editor to load the packet again
|
||||
func (p *Packet) FileChanged(name string) {
|
||||
p.Load()
|
||||
}
|
||||
|
||||
func (p *Packet) Load() error {
|
||||
return loaders.LoadJSON(p.Get().([]byte), p)
|
||||
}
|
||||
|
||||
func (p *Packet) MarshalTo() json.Unmarshaler {
|
||||
return p.marshalTo
|
||||
}
|
||||
|
||||
func (p *Packet) UnmarshalJSON(data []byte) error {
|
||||
return p.marshalTo.UnmarshalJSON(data)
|
||||
}
|
||||
|
||||
// Returns packet type(settings, commands, keymaps, ...)
|
||||
func (p *Packet) group() string {
|
||||
for _, key := range types {
|
||||
if strings.Contains(filepath.Ext(p.Name()), key) {
|
||||
return key
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Returns Packets with specific type
|
||||
func (p Packets) Filter(key string) Packets {
|
||||
var pckts Packets
|
||||
for _, pckt := range p {
|
||||
if strings.Contains(filepath.Ext(pckt.Name()), key) {
|
||||
pckts = append(pckts, pckt)
|
||||
}
|
||||
}
|
||||
return pckts
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPacket(t *testing.T) {
|
||||
tests := []struct {
|
||||
path string
|
||||
data string
|
||||
}{
|
||||
{
|
||||
"testdata/Vintage/Vintageous.sublime-settings",
|
||||
"Testing packages",
|
||||
},
|
||||
}
|
||||
for i, test := range tests {
|
||||
s := NewPacket(test.path, nil)
|
||||
d, err := ioutil.ReadFile(test.path)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Can't read file: %s", i, err)
|
||||
}
|
||||
if d1 := s.Get().([]byte); string(d) != string(d1) {
|
||||
t.Errorf("Test %d: Expected %v but, got %s", i, d, string(d1))
|
||||
}
|
||||
if err := ioutil.WriteFile(test.path, []byte(test.data), 0644); err != nil {
|
||||
t.Fatalf("Test %d: Can't write file: %s", i, err)
|
||||
}
|
||||
s.Load()
|
||||
if d1 := s.Get().([]byte); test.data != string(d1) {
|
||||
t.Errorf("Test %d: Expected %s but, got %s", i, test.data, string(d1))
|
||||
}
|
||||
if err := ioutil.WriteFile(test.path, d, 0644); err != nil {
|
||||
t.Fatalf("Test %d: Can't write back file: %s", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackets(t *testing.T) {
|
||||
test := struct {
|
||||
pckts []string
|
||||
expect map[string][]string
|
||||
}{
|
||||
[]string{
|
||||
"testdata/Default.sublime-settings",
|
||||
"testdata/Vintage/Default.sublime-keymap",
|
||||
"testdata/Vintage/Vintageous.sublime-settings",
|
||||
},
|
||||
map[string][]string{
|
||||
"setting": {
|
||||
"testdata/Default.sublime-settings",
|
||||
"testdata/Vintage/Vintageous.sublime-settings",
|
||||
},
|
||||
"keymap": {
|
||||
"testdata/Vintage/Default.sublime-keymap",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
packets := Packets{}
|
||||
for _, p := range test.pckts {
|
||||
packets = append(packets, NewPacket(p, nil))
|
||||
}
|
||||
for key, ns := range test.expect {
|
||||
ps := packets.Filter(key)
|
||||
for _, p := range ns {
|
||||
found := false
|
||||
for _, p1 := range ps {
|
||||
if p == p1.Name() {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Expected to find %s in %s plugin packets", p, ps)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,122 +0,0 @@
|
||||
// Copyright 2013 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"github.com/limetext/lime/backend/keys"
|
||||
"github.com/limetext/lime/backend/log"
|
||||
"github.com/limetext/text"
|
||||
"os"
|
||||
pt "path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
// Plugin is a Package type containing some files
|
||||
// with specific suffix that could be interpreted by
|
||||
// lime text api(currently python) and some
|
||||
// settings, snippets, commands and etc as packets
|
||||
Plugin struct {
|
||||
text.HasSettings
|
||||
keys.HasKeyBindings
|
||||
path string
|
||||
suffix string
|
||||
files []os.FileInfo
|
||||
defaultSettings *text.HasSettings
|
||||
platformSettings *text.HasSettings
|
||||
defaultBindings *keys.HasKeyBindings
|
||||
}
|
||||
)
|
||||
|
||||
// Initializes a new plugin whith loading all of the
|
||||
// settings, keymaps and etc. Suffix variable show's
|
||||
// which file types we need for plugin for example if
|
||||
// the plugin is written in python the suffix should
|
||||
// be ".py". We will use this function at initialization
|
||||
// to add user plugins and on new_plugin command
|
||||
func NewPlugin(path string, suffix string) (p *Plugin) {
|
||||
p = &Plugin{path: path, suffix: suffix}
|
||||
p.defaultSettings = new(text.HasSettings)
|
||||
p.platformSettings = new(text.HasSettings)
|
||||
p.defaultBindings = new(keys.HasKeyBindings)
|
||||
|
||||
p.Settings().SetParent(p.platformSettings)
|
||||
p.platformSettings.Settings().SetParent(p.defaultSettings)
|
||||
|
||||
p.KeyBindings().SetParent(p.defaultBindings)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Plugin) Name() string {
|
||||
return p.path
|
||||
}
|
||||
|
||||
// Returns slice of files with plugin suffix
|
||||
// loaded at initialization
|
||||
func (p *Plugin) Get() interface{} {
|
||||
return p.files
|
||||
}
|
||||
|
||||
// On plugin reload we will scan for plugin files
|
||||
// and packets in plugin path
|
||||
func (p *Plugin) Reload() {
|
||||
var files []os.FileInfo
|
||||
log.Info("Reloading plugin %s", p.Name())
|
||||
f, err := os.Open(p.path)
|
||||
if err != nil {
|
||||
log.Errorf("Couldn't open dir: %s", err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
fi, err := f.Readdir(-1)
|
||||
if err != nil {
|
||||
log.Errorf("Couldn't read dir: %s", err)
|
||||
return
|
||||
}
|
||||
for _, f := range fi {
|
||||
if p.suffix != "" && strings.HasSuffix(f.Name(), p.suffix) {
|
||||
files = append(files, f)
|
||||
}
|
||||
}
|
||||
p.files = files
|
||||
}
|
||||
|
||||
// Scaning path for finding plugins that contain files
|
||||
// whith specific suffix
|
||||
func ScanPlugins(path string, suffix string) []*Plugin {
|
||||
var plugins []*Plugin
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
return nil
|
||||
}
|
||||
defer f.Close()
|
||||
dirs, err := f.Readdirnames(-1)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
return nil
|
||||
}
|
||||
for _, dir := range dirs {
|
||||
dir2 := pt.Join(path, dir)
|
||||
f2, err := os.Open(dir2)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
continue
|
||||
}
|
||||
defer f2.Close()
|
||||
fi, err := f2.Readdir(-1)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, f := range fi {
|
||||
fn := f.Name()
|
||||
if strings.HasSuffix(fn, suffix) {
|
||||
plugins = append(plugins, NewPlugin(dir2, suffix))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return plugins
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
// Copyright 2014 The lime Authors.
|
||||
// Use of this source code is governed by a 2-clause
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPlugin(t *testing.T) {
|
||||
tests := []struct {
|
||||
path string
|
||||
suffix string
|
||||
files []string
|
||||
}{
|
||||
{
|
||||
"testdata/Vintage",
|
||||
".py",
|
||||
[]string{"action_cmds.py", "state.py", "transformers.py"},
|
||||
},
|
||||
}
|
||||
for i, test := range tests {
|
||||
p := NewPlugin(test.path, test.suffix)
|
||||
p.Reload()
|
||||
if p.Name() != test.path {
|
||||
t.Errorf("Test %d: Expected plugin name %s but, got %s", i, test.path, p.Name())
|
||||
}
|
||||
for _, f := range test.files {
|
||||
found := false
|
||||
for _, fi := range p.Get().([]os.FileInfo) {
|
||||
if f == fi.Name() {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Test %d: Expected to find %s in %s plugin", i, f, p.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginReload(t *testing.T) {
|
||||
p := NewPlugin("testdata/Closetag", ".vim")
|
||||
if err := ioutil.WriteFile("testdata/Closetag/test.vim", []byte("testing"), 0644); err != nil {
|
||||
t.Fatalf("Couldn't write file: %s", err)
|
||||
}
|
||||
p.Reload()
|
||||
fi := p.Get().([]os.FileInfo)
|
||||
found := false
|
||||
for _, f := range fi {
|
||||
if f.Name() == "test.vim" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Expected to find test.vim file in %s", p.Name())
|
||||
}
|
||||
os.Remove("testdata/Closetag/test.vim")
|
||||
}
|
||||
|
||||
func TestScanPlugins(t *testing.T) {
|
||||
tests := []struct {
|
||||
path string
|
||||
suffix string
|
||||
expect []string
|
||||
}{
|
||||
{
|
||||
"testdata/",
|
||||
".py",
|
||||
[]string{
|
||||
"testdata/Vintage",
|
||||
},
|
||||
},
|
||||
{
|
||||
"testdata/",
|
||||
".vim",
|
||||
[]string{
|
||||
"testdata/Closetag",
|
||||
},
|
||||
},
|
||||
}
|
||||
for i, test := range tests {
|
||||
plugins := ScanPlugins(test.path, test.suffix)
|
||||
for _, f := range test.expect {
|
||||
found := false
|
||||
for _, p := range plugins {
|
||||
if f == p.Name() {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Test %d: Expected ScanPlugins find %s plugin", i, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
334
backend/packages/testdata/Closetag/closetag.vim
vendored
334
backend/packages/testdata/Closetag/closetag.vim
vendored
@ -1,334 +0,0 @@
|
||||
" File: closetag.vim
|
||||
" Summary: Functions and mappings to close open HTML/XML tags
|
||||
" Uses: <C-_> -- close matching open tag
|
||||
" Author: Steven Mueller <diffusor@ugcs.caltech.edu>
|
||||
" Last Modified: Tue May 24 13:29:48 PDT 2005
|
||||
" Version: 0.9.1
|
||||
" XXX - breaks if close attempted while XIM is in preedit mode
|
||||
" TODO - allow usability as a global plugin -
|
||||
" Add g:unaryTagsStack - always contains html tags settings
|
||||
" and g:closetag_default_xml - user should define this to default to xml
|
||||
" When a close is attempted but b:unaryTagsStack undefined,
|
||||
" use b:closetag_html_style to determine if the file is to be treated
|
||||
" as html or xml. Failing that, check the filetype for xml or html.
|
||||
" Finally, default to g:closetag_html_style.
|
||||
" If the file is html, let b:unaryTagsStack=g:unaryTagsStack
|
||||
" otherwise, let b:unaryTagsStack=""
|
||||
" TODO - make matching work for all comments
|
||||
" -- kinda works now, but needs syn sync minlines to be very long
|
||||
" -- Only check whether in syntax in the beginning, then store comment tags
|
||||
" in the tagstacks to determine whether to move into or out of comment mode
|
||||
" TODO - The new normal mode mapping clears recent messages with its <ESC>, and
|
||||
" it doesn't fix the null-undo issue for vim 5.7 anyway.
|
||||
" TODO - make use of the following neat features:
|
||||
" -- the ternary ?: operator
|
||||
" -- :echomsg and :echoerr
|
||||
" -- curly brace expansion for variables and function name definitions?
|
||||
" -- check up on map <blah> \FuncName
|
||||
"
|
||||
" Description:
|
||||
" This script eases redundant typing when writing html or xml files (even if
|
||||
" you're very good with ctrl-p and ctrl-n :). Hitting ctrl-_ will initiate a
|
||||
" search for the most recent open tag above that is not closed in the
|
||||
" intervening space and then insert the matching close tag at the cursor. In
|
||||
" normal mode, the close tag is inserted one character after cursor rather than
|
||||
" at it, as if a<C-_> had been used. This allows putting close tags at the
|
||||
" ends of lines while in normal mode, but disallows inserting them in the
|
||||
" first column.
|
||||
"
|
||||
" For HTML, a configurable list of tags are ignored in the matching process.
|
||||
" By default, the following tags will not be matched and thus not closed
|
||||
" automatically: area, base, br, dd, dt, hr, img, input, link, meta, and
|
||||
" param.
|
||||
"
|
||||
" For XML, all tags must have a closing match or be terminated by />, as in
|
||||
" <empty-element/>. These empty element tags are ignored for matching.
|
||||
"
|
||||
" Comment checking is now handled by vim's internal syntax checking. If tag
|
||||
" closing is initiated outside a comment, only tags outside of comments will
|
||||
" be matched. When closing tags in comments, only tags within comments will
|
||||
" be matched, skipping any non-commented out code (wee!). However, the
|
||||
" process of determining the syntax ID of an arbitrary position can still be
|
||||
" erroneous if a comment is not detected because the syntax highlighting is
|
||||
" out of sync, or really slow if syn sync minlines is large.
|
||||
" Set the b:closetag_disable_synID variable to disable this feature if you
|
||||
" have really big chunks of comment in your code and closing tags is too slow.
|
||||
"
|
||||
" If syntax highlighting is not enabled, comments will not be handled very
|
||||
" well. Commenting out HTML in certain ways may cause a "tag mismatch"
|
||||
" message and no completion. For example, '<!--a href="blah">link!</a-->'
|
||||
" between the cursor and the most recent unclosed open tag above causes
|
||||
" trouble. Properly matched well formed tags in comments don't cause a
|
||||
" problem.
|
||||
"
|
||||
" Install:
|
||||
" To use, place this file in your standard vim scripts directory, and source
|
||||
" it while editing the file you wish to close tags in. If the filetype is not
|
||||
" set or the file is some sort of template with embedded HTML, you may force
|
||||
" HTML style tag matching by first defining the b:closetag_html_style buffer
|
||||
" variable. Otherwise, the default is XML style tag matching.
|
||||
"
|
||||
" Example:
|
||||
" :let b:closetag_html_style=1
|
||||
" :source ~/.vim/scripts/closetag.vim
|
||||
"
|
||||
" For greater convenience, load this script in an autocommand:
|
||||
" :au Filetype html,xml,xsl source ~/.vim/scripts/closetag.vim
|
||||
"
|
||||
" Also, set noignorecase for html files or edit b:unaryTagsStack to match your
|
||||
" capitalization style. You may set this variable before or after loading the
|
||||
" script, or simply change the file itself.
|
||||
"
|
||||
" Configuration Variables:
|
||||
"
|
||||
" b:unaryTagsStack Buffer local string containing a whitespace
|
||||
" seperated list of element names that should be
|
||||
" ignored while finding matching closetags. Checking
|
||||
" is done according to the current setting of the
|
||||
" ignorecase option.
|
||||
"
|
||||
" b:closetag_html_style Define this (as with let b:closetag_html_style=1)
|
||||
" and source the script again to set the
|
||||
" unaryTagsStack to its default value for html.
|
||||
"
|
||||
" b:closetag_disable_synID Define this to disable comment checking if tag
|
||||
" closing is too slow. This can be set or unset
|
||||
" without having to source again.
|
||||
"
|
||||
" Changelog:
|
||||
" May 24, 2005 Tuesday
|
||||
" * Changed function names to be script-local to avoid conflicts with other
|
||||
" scripts' stack implementations.
|
||||
"
|
||||
" June 07, 2001 Thursday
|
||||
" * Added comment handling. Currently relies on synID, so if syn sync
|
||||
" minlines is small, the chance for failure is high, but if minlines is
|
||||
" large, tagclosing becomes rather slow...
|
||||
"
|
||||
" * Changed normal mode closetag mapping to use <C-R> in insert mode
|
||||
" rather than p in normal mode. This has 2 implications:
|
||||
" - Tag closing no longer clobbers the unnamed register
|
||||
" - When tag closing fails or finds no match, no longer adds to the undo
|
||||
" buffer for recent vim 6.0 development versions.
|
||||
" - However, clears the last message when closing tags in normal mode
|
||||
"
|
||||
" * Changed the closetag_html_style variable to be buffer-local rather than
|
||||
" global.
|
||||
"
|
||||
" * Expanded documentation
|
||||
|
||||
"------------------------------------------------------------------------------
|
||||
" User configurable settings
|
||||
"------------------------------------------------------------------------------
|
||||
|
||||
" if html, don't close certain tags. Works best if ignorecase is set.
|
||||
" otherwise, capitalize these elements according to your html editing style
|
||||
if !exists("b:unaryTagsStack") || exists("b:closetag_html_style")
|
||||
if &filetype == "html" || exists("b:closetag_html_style")
|
||||
let b:unaryTagsStack="area base br dd dt hr img input link meta param"
|
||||
else " for xsl and xsl
|
||||
let b:unaryTagsStack=""
|
||||
endif
|
||||
endif
|
||||
|
||||
" Has this already been loaded?
|
||||
if exists("loaded_closetag")
|
||||
finish
|
||||
endif
|
||||
let loaded_closetag=1
|
||||
|
||||
" set up mappings for tag closing
|
||||
inoremap </ <C-R>=GetCloseTag()<CR>
|
||||
map <C-_> a<C-_><ESC>
|
||||
|
||||
"------------------------------------------------------------------------------
|
||||
" Tag closer - uses the stringstack implementation below
|
||||
"------------------------------------------------------------------------------
|
||||
|
||||
" Returns the most recent unclosed tag-name
|
||||
" (ignores tags in the variable referenced by a:unaryTagsStack)
|
||||
function! GetLastOpenTag(unaryTagsStack)
|
||||
" Search backwards through the file line by line using getline()
|
||||
" Overall strategy (moving backwards through the file from the cursor):
|
||||
" Push closing tags onto a stack.
|
||||
" On an opening tag, if the tag matches the stack top, discard both.
|
||||
" -- if the tag doesn't match, signal an error.
|
||||
" -- if the stack is empty, use this tag
|
||||
let linenum=line(".")
|
||||
let lineend=col(".") - 1 " start: cursor position
|
||||
let first=1 " flag for first line searched
|
||||
let b:TagStack="" " main stack of tags
|
||||
let startInComment=s:InComment()
|
||||
|
||||
let tagpat='</\=\(\k\|[-:]\)\+\|/>'
|
||||
" Search for: closing tags </tag, opening tags <tag, and unary tag ends />
|
||||
while (linenum>0)
|
||||
" Every time we see an end-tag, we push it on the stack. When we see an
|
||||
" open tag, if the stack isn't empty, we pop it and see if they match.
|
||||
" If no, signal an error.
|
||||
" If yes, continue searching backwards.
|
||||
" If stack is empty, return this open tag as the one that needs closing.
|
||||
let line=getline(linenum)
|
||||
if first
|
||||
let line=strpart(line,0,lineend)
|
||||
else
|
||||
let lineend=strlen(line)
|
||||
endif
|
||||
let b:lineTagStack=""
|
||||
let mpos=0
|
||||
let b:TagCol=0
|
||||
" Search the current line in the forward direction, pushing any tags
|
||||
" onto a special stack for the current line
|
||||
while (mpos > -1)
|
||||
let mpos=matchend(line,tagpat)
|
||||
if mpos > -1
|
||||
let b:TagCol=b:TagCol+mpos
|
||||
let tag=matchstr(line,tagpat)
|
||||
|
||||
if exists("b:closetag_disable_synID") || startInComment==s:InCommentAt(linenum, b:TagCol)
|
||||
let b:TagLine=linenum
|
||||
call s:Push(matchstr(tag,'[^<>]\+'),"b:lineTagStack")
|
||||
endif
|
||||
"echo "Tag: ".tag." ending at position ".mpos." in '".line."'."
|
||||
let lineend=lineend-mpos
|
||||
let line=strpart(line,mpos,lineend)
|
||||
endif
|
||||
endwhile
|
||||
" Process the current line stack
|
||||
while (!s:EmptystackP("b:lineTagStack"))
|
||||
let tag=s:Pop("b:lineTagStack")
|
||||
if match(tag, "^/") == 0 "found end tag
|
||||
call s:Push(tag,"b:TagStack")
|
||||
"echo linenum." ".b:TagStack
|
||||
elseif s:EmptystackP("b:TagStack") && !s:Instack(tag, a:unaryTagsStack) "found unclosed tag
|
||||
return tag
|
||||
else
|
||||
let endtag=s:Peekstack("b:TagStack")
|
||||
if endtag == "/".tag || endtag == "/"
|
||||
call s:Pop("b:TagStack") "found a open/close tag pair
|
||||
"echo linenum." ".b:TagStack
|
||||
elseif !s:Instack(tag, a:unaryTagsStack) "we have a mismatch error
|
||||
echohl Error
|
||||
echon "\rError:"
|
||||
echohl None
|
||||
echo " tag mismatch: <".tag."> doesn't match <".endtag.">. (Line ".linenum." Tagstack: ".b:TagStack.")"
|
||||
return ""
|
||||
endif
|
||||
endif
|
||||
endwhile
|
||||
let linenum=linenum-1 | let first=0
|
||||
endwhile
|
||||
" At this point, we have exhausted the file and not found any opening tag
|
||||
echo "No opening tags."
|
||||
return ""
|
||||
endfunction
|
||||
|
||||
" Returns closing tag for most recent unclosed tag, respecting the
|
||||
" current setting of b:unaryTagsStack for tags that should not be closed
|
||||
function! GetCloseTag()
|
||||
if !exists("b:unaryTagsStack") || exists("b:closetag_html_style")
|
||||
if &filetype == "html" || exists("b:closetag_html_style")
|
||||
let b:unaryTagsStack="area base br dd dt hr img input link meta param"
|
||||
else " for xsl and xsl
|
||||
let b:unaryTagsStack=""
|
||||
endif
|
||||
endif
|
||||
let tag=GetLastOpenTag("b:unaryTagsStack")
|
||||
if tag == ""
|
||||
return ""
|
||||
else
|
||||
return "</".tag.">"
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" return 1 if the cursor is in a syntactically identified comment field
|
||||
" (fails for empty lines: always returns not-in-comment)
|
||||
function! s:InComment()
|
||||
return synIDattr(synID(line("."), col("."), 0), "name") =~ 'Comment'
|
||||
endfunction
|
||||
|
||||
" return 1 if the position specified is in a syntactically identified comment field
|
||||
function! s:InCommentAt(line, col)
|
||||
return synIDattr(synID(a:line, a:col, 0), "name") =~ 'Comment'
|
||||
endfunction
|
||||
|
||||
"------------------------------------------------------------------------------
|
||||
" String Stacks
|
||||
"------------------------------------------------------------------------------
|
||||
" These are strings of whitespace-separated elements, matched using the \< and
|
||||
" \> patterns after setting the iskeyword option.
|
||||
"
|
||||
" The sname argument should contain a symbolic reference to the stack variable
|
||||
" on which method should operate on (i.e., sname should be a string containing
|
||||
" a fully qualified (ie: g:, b:, etc) variable name.)
|
||||
|
||||
" Helper functions
|
||||
function! s:SetKeywords()
|
||||
let g:IsKeywordBak=&iskeyword
|
||||
let &iskeyword="33-255"
|
||||
endfunction
|
||||
|
||||
function! s:RestoreKeywords()
|
||||
let &iskeyword=g:IsKeywordBak
|
||||
endfunction
|
||||
|
||||
" Push el onto the stack referenced by sname
|
||||
function! s:Push(el, sname)
|
||||
if !s:EmptystackP(a:sname)
|
||||
exe "let ".a:sname."=a:el.' '.".a:sname
|
||||
else
|
||||
exe "let ".a:sname."=a:el"
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" Check whether the stack is empty
|
||||
function! s:EmptystackP(sname)
|
||||
exe "let stack=".a:sname
|
||||
if match(stack,"^ *$") == 0
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" Return 1 if el is in stack sname, else 0.
|
||||
function! s:Instack(el, sname)
|
||||
exe "let stack=".a:sname
|
||||
call s:SetKeywords()
|
||||
let m=match(stack, "\\<".a:el."\\>")
|
||||
call s:RestoreKeywords()
|
||||
if m < 0
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" Return the first element in the stack
|
||||
function! s:Peekstack(sname)
|
||||
call s:SetKeywords()
|
||||
exe "let stack=".a:sname
|
||||
let top=matchstr(stack, "\\<.\\{-1,}\\>")
|
||||
call s:RestoreKeywords()
|
||||
return top
|
||||
endfunction
|
||||
|
||||
" Remove and return the first element in the stack
|
||||
function! s:Pop(sname)
|
||||
if s:EmptystackP(a:sname)
|
||||
echo "Error! Stack ".a:sname." is empty and can't be popped."
|
||||
return ""
|
||||
endif
|
||||
exe "let stack=".a:sname
|
||||
" Find the first space, loc is 0-based. Marks the end of 1st elt in stack.
|
||||
call s:SetKeywords()
|
||||
let loc=matchend(stack,"\\<.\\{-1,}\\>")
|
||||
exe "let ".a:sname."=strpart(stack, loc+1, strlen(stack))"
|
||||
let top=strpart(stack, match(stack, "\\<"), loc)
|
||||
call s:RestoreKeywords()
|
||||
return top
|
||||
endfunction
|
||||
|
||||
function! s:Clearstack(sname)
|
||||
exe "let ".a:sname."=''"
|
||||
endfunction
|
@ -1,5 +0,0 @@
|
||||
[
|
||||
{ "keys": ["i"], "command": "test1", "context": [{ "key": "test1" }] },
|
||||
{ "keys": ["i"], "command": "test2", "context": [{ "key": "test2" }] },
|
||||
{ "keys": ["i"], "command": "test3", "context": [{ "key": "test3" }] },
|
||||
]
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user