Skip to content

Commit

Permalink
Initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
driusan committed May 16, 2016
0 parents commit fc3f711
Show file tree
Hide file tree
Showing 21 changed files with 1,954 additions and 0 deletions.
21 changes: 21 additions & 0 deletions LICENSE
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2016 Dave MacFarlane

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
38 changes: 38 additions & 0 deletions README.md
@@ -0,0 +1,38 @@
# The de Editor

de is a programmer's editor, where that programmer happens to be [driusan](https://github.com/driusan/).

It's kind of like a bastard child of vim and Plan 9's ACME editor, because vim feels inadequate on a
computer with a mouse after using acme, and acme feels inadequate on a computer with a keyboard after
using vi.

Like vim, it's a modal editor with syntax highlighting that uses hjkl for movement.
Like acme, it attempts to exploit your current environment instead of replacing it and tries to
make the mouse useful.

See [USAGE.md](USAGE.md) for usage instructions.

## Features

* Syntax highlighting (currently Go only)
* vi-like keybindings and philosophy
* acme-like mouse bindings and philosophy


## Limitations and Bugs

* vi-like functionality not fully implemented (most notably repeating a command by prefixing it
with a number, and some movement verbs like '%' are missing.)
* Can not open multiple files/windows at a time. (if your workflow is like mine, it means you often
save and quit, do something in the shell, and then relaunch your editor. The startup time should
be fast enough to support this style of workflow.)
* Missing acme style window tag to use as a scratch space.

# Installation

It should be installable with the standard go tools:

```
go get github.com/driusan/de
```

70 changes: 70 additions & 0 deletions USAGE.md
@@ -0,0 +1,70 @@
# de Usage Instructions

## Basic Usage
de is a modal editor that starts in normal mode. The three modes currently implemented are
Normal (biege background), Insert (light green background), and Delete mode (light red/pink
background). The keybindings are inspired by, but not identical to, vi.

In Normal and Delete modes, the movement commands are similar to vi. hjkl move the cursor.
Shift-6 (^) moves the cursor to the start of the line, and Shift-4 ($) moves the cursor to the
end of the line, G moves to end of file (or a line number if you type the line number before hitting
G). w moves the cursor to the next word. In Delete mode it will delete from the current cursor up
to that point, and in Normal mode it will either move to, or select up to that point depending on if
the ctrl key is pressed. Other movement commands will be implemented, but that's all I've done up to now.
Unlike in vi, the h and l keys do not stop at line boundaries. p will insert the most recently
delete text, replacing the currently selected text.

Generally, when I find myself typing a keystroke out of muscle-memory enough times as a long
time vi-user, I implement it, or a close enough approximation to it, here. Ranges with a repeat
(ie 3dw) are not yet implemented, but will be eventually.

In Normal mode you can enter Insert mode by pressing 'i' or Delete mode by pressing 'd' and
the background colour should change to indicate the current mode.

In Insert mode, the arrow keys take on the same meaning as hjkl in Normal mode, and Escape will
return to normal mode. Any other key combination that results in a printable unicode character
being sent to de will insert the utf8 encoding of that character at the current location of the
file. In all other modes, the arrow keys scroll the viewport without adjusting the cursor.

In all modes, backspace will delete the currently selected text (or previous character if nothing
is selected) without changing the mode.

Pressing the Escape key will save the current file and exit. *NOTE TO VI USERS: RE-READ THE LAST
SENTENCE*

## Mouse Usage

While the keyboard usage is inspired by vim, the mouse usage is inspired by acme.
The mouse works the same way regardless of keyboard mode.

Clicking anywhere with the left mouse button will move the text cursor or select text.

Clicking with the right mouse button will search for the next instance of the word clicked on
and select the next instance found. If the word is a filename, changes to the current file will be
discarded and the clicked file will be opened in the current window. The keyboard equivalent
for the currently selected text is the slash key (although slash will not open a file.)

Clicking with the middle mouse button will "execute" the word clicked on. (see below.)

Chording will probably eventually work similarly to acme, but isn't yet implemented.

## Executing Words

Words that are selected or clicked on can be "executed" to control the editor, either by
selecting the word and then pressing the Enter key, or by clicking with the middle mouse button.
(When executing with the keyboard, it will first check if the file exists and open it if applicable,
similarly to searching with the mouse.)

If executing a point in a word instead of a selection, that word will be executed.

If the word is an internal editor command, it will perform that command. Otherwise, the shell command
will be executed and the output will replace the currently selected text.

Currently understood commands:
Get (or Discard): Reload the current file from disk and discard changes
Put (or Save): Save the current character buffer to disk, overwriting the existing file.
Exit (or Quit): Quit de, discarding any changes.

To be implemented:
Copy (or Snarf), Cut, Paste,
ACME style tag/Scratch line
55 changes: 55 additions & 0 deletions actions/deletecursor.go
@@ -0,0 +1,55 @@
package actions

import (
"github.com/driusan/de/demodel"
)

func DeleteCursor(From, To demodel.Position, buff *demodel.CharBuffer) {
if buff == nil {
return
}

dot := demodel.Dot{}
i, err := From(*buff)
if err != nil {
return
}
dot.Start = i

i, err = To(*buff)
if err != nil {
return
}
dot.End = i
if dot.Start == dot.End {
// nothing selected, so delete the previous character

if dot.Start == 0 {
// can't delete past the beginning
return
}

buff.Buffer = append(
buff.Buffer[:dot.Start-1], buff.Buffer[dot.Start:]...,
)

// now adjust dot if it was inside the deleted range..
if buff.Dot.Start == dot.Start {
buff.Dot.Start -= 1
buff.Dot.End = buff.Dot.Start
}
return
} else {
// delete the selected text.
buff.SnarfBuffer = make([]byte, dot.End-dot.Start)
copy(buff.SnarfBuffer, buff.Buffer[dot.Start:dot.End])

buff.Buffer = append(buff.Buffer[:dot.Start], buff.Buffer[dot.End:]...)

// now adjust dot if it was inside the deleted range..
if buff.Dot.Start >= dot.Start && buff.Dot.End <= dot.End {
buff.Dot.Start = dot.Start
buff.Dot.End = buff.Dot.Start
}
}
}
110 changes: 110 additions & 0 deletions actions/execute.go
@@ -0,0 +1,110 @@
package actions

import (
"fmt"
"github.com/driusan/de/demodel"
"io/ioutil"
"os"
"os/exec"
)

func PerformAction(From, To demodel.Position, buff *demodel.CharBuffer) {
if buff == nil {
return
}

dot := demodel.Dot{}
i, err := From(*buff)
if err != nil {
return
}

dot.Start = i
i, err = To(*buff)
if err != nil {
return
}
dot.End = i

cmd := string(buff.Buffer[dot.Start : dot.End+1])
runOrExec(cmd, buff)
}

func OpenOrPerformAction(From, To demodel.Position, buff *demodel.CharBuffer) {
if buff == nil {
return
}

dot := demodel.Dot{}
i, err := From(*buff)
if err != nil {
return
}

dot.Start = i
i, err = To(*buff)
if err != nil {
return
}
dot.End = i

var cmd string
if dot.End+1 >= uint(len(buff.Buffer)) {
cmd = string(buff.Buffer[dot.Start:])
} else {
cmd = string(buff.Buffer[dot.Start : dot.End+1])
}

if _, err := os.Stat(cmd); err == nil {
// the file exists, so open it
b, ferr := ioutil.ReadFile(cmd)
if ferr != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
return
}
buff.Buffer = b
buff.Filename = cmd
buff.Dot.Start = 0
buff.Dot.End = 0
return
}
runOrExec(cmd, buff)
}

func runOrExec(cmd string, buff *demodel.CharBuffer) {

if f, ok := actions[cmd]; ok {
// it was an internal command, so run it.
f("", buff)
return
}

// it wasn't an internal command, so run the shell command
gocmd := exec.Command(cmd)
stdout, err := gocmd.StdoutPipe()
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
return
}
if err := gocmd.Start(); err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
return
}
output, err := ioutil.ReadAll(stdout)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
return
}
if err := gocmd.Wait(); err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
}
// nothing selected, so insert at dot.Start
newBuffer := make([]byte, len(buff.Buffer)-int((buff.Dot.End-buff.Dot.Start))+len(output))
copy(newBuffer, buff.Buffer)
copy(newBuffer[buff.Dot.Start:], output)
copy(newBuffer[int(buff.Dot.Start)+len(output):], buff.Buffer[buff.Dot.End:])

buff.Buffer = newBuffer

fmt.Printf("Output: %s\n", string(output))
}
84 changes: 84 additions & 0 deletions actions/find.go
@@ -0,0 +1,84 @@
package actions

import (
"fmt"
"github.com/driusan/de/demodel"
"io/ioutil"
"os"
)

// Changes Dot to next instance of the character sequence between From
// and To
func FindNext(From, To demodel.Position, buff *demodel.CharBuffer) {
if buff == nil {
return
}
dot := demodel.Dot{}
i, err := From(*buff)
if err != nil {
return
}
dot.Start = i

i, err = To(*buff)
if err != nil {
return
}
dot.End = i + 1

word := string(buff.Buffer[dot.Start:dot.End])
lenword := dot.End - dot.Start
for i := dot.End; i < uint(len(buff.Buffer))-lenword; i++ {
if string(buff.Buffer[i:i+lenword]) == word {
buff.Dot.Start = i
buff.Dot.End = i + lenword - 1
return
}
}

}

func FindNextOrOpen(From, To demodel.Position, buff *demodel.CharBuffer) {
if buff == nil {
return
}
dot := demodel.Dot{}
i, err := From(*buff)
if err != nil {
return
}
dot.Start = i

i, err = To(*buff)
if err != nil {
return
}
dot.End = i + 1

word := string(buff.Buffer[dot.Start:dot.End])

fmt.Printf("Word: %s\n", word)
if _, err := os.Stat(word); err == nil {
// the file exists, so open it
b, ferr := ioutil.ReadFile(word)
if ferr != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
return
}
buff.Buffer = b
buff.Filename = word
buff.Dot.Start = 0
buff.Dot.End = 0
return
}

// the file doesn't exist, so find the next instance of word.
lenword := dot.End - dot.Start
for i := dot.End; i < uint(len(buff.Buffer))-lenword; i++ {
if string(buff.Buffer[i:i+lenword]) == word {
buff.Dot.Start = i
buff.Dot.End = i + lenword - 1
return
}
}
}

0 comments on commit fc3f711

Please sign in to comment.