Switch the main project over to a meta project.

This commit is contained in:
Felix Laurie von Massenbach 2015-04-25 21:44:55 +01:00
parent 7eb4511afb
commit 1f1303a388
267 changed files with 15 additions and 54356 deletions

4
.gitattributes vendored
View File

@ -1,4 +0,0 @@
backend/textmate/testdata/* -text
backend/testdata/* -text
backend/loaders/json/testdata/* -text
backend/loaders/plist/testdata/* -text

12
.gitignore vendored
View File

@ -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
View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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).

View File

@ -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
}

View File

@ -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())
}
}

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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{},
})
}

View File

@ -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)
}

View File

@ -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{},
})
}

View File

@ -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)
}
}
}

View File

@ -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{},
})
}

View File

@ -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)
}

View File

@ -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

View File

@ -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{},
})
}

View File

@ -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()
}
}

View File

@ -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{},
})
}

View File

@ -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")
}

View File

@ -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{},
})
}

View File

@ -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)
}
}

View File

@ -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{},
})
}

View File

@ -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")
}

View File

@ -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{},
})
}

View File

@ -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)
}
}
}

View File

@ -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{},
})
}

View File

@ -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())
}
}
}
}
}

View File

@ -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

View File

@ -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{}},
})
}

View File

@ -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")
}
}

View File

@ -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)
}
}
}

View File

@ -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)
}
}

View File

@ -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{},
})
}

View File

@ -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)
}
}

View File

@ -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{},
})
}

View File

@ -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")
}

View File

@ -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{},
})
}

View File

@ -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)
}
}

View File

@ -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{},
})
}

View File

@ -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)
}

View File

@ -1 +0,0 @@
Some Text

View File

@ -1 +0,0 @@
Some More Text

View File

@ -1 +0,0 @@
Some Text

View File

@ -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{},
})
}

View File

@ -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
}
}
}
}

View File

@ -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{}},
})
}

View File

@ -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()}))
}
}

View File

@ -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{},
})
}

View File

@ -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")
}
}

View File

@ -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{}},
})
}

View File

@ -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()))
}
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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)
})
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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())
}
}

View File

@ -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()
}

View File

@ -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())
}
}

View File

@ -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
}

View File

@ -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")
}
}

View File

@ -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
}

View File

@ -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())
}
}

View File

@ -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" }] },
]

View File

@ -1,4 +0,0 @@
[
{ "keys": ["ctrl+d", "ctrl+k"], "command": "t1", "context": [{ "key": "t1" }] },
{ "keys": ["p"], "command": "t2", "context": [{ "key": "t2" }] },
]

View File

@ -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(&regionSetAdjuster{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)
}

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -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"}
]

View File

@ -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

View File

@ -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, "&gt;", ">", -1)
n = strings.Replace(n, "&lt;", "<", -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)
}

View File

@ -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 <- !.

View File

@ -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
}

View File

@ -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)
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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()
}

View File

@ -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()
}

View File

@ -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()
}

View File

@ -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()
}

View File

@ -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...)
}

View File

@ -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")
}

View File

@ -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
)

View File

@ -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()
}

View File

@ -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())
}

View File

@ -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)
}

View File

@ -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()
}

View File

@ -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"}

View File

@ -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
}

View File

@ -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)
}
}
}
}

View File

@ -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
}

View File

@ -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)
}
}
}
}

View File

@ -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

View File

@ -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