Skip to content

Commit

Permalink
Added tagline support.
Browse files Browse the repository at this point in the history
The tagline works similarly to acme, where you can use it as a scratch
space for writing commands. It can be accessed with either the mouse
pointer (like acme), or by typing ; or :. This allows it to double as
something like a vi ex-mode as well as an acme tagline (though ex commands
aren't implemented, someone could theoretically add a plugin which adds them.)
  • Loading branch information
driusan committed May 17, 2016
1 parent b475636 commit 3b53da8
Show file tree
Hide file tree
Showing 16 changed files with 643 additions and 73 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
de
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ See [USAGE.md](USAGE.md) for usage instructions.
* 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

Expand All @@ -35,3 +34,5 @@ It should be installable with the standard go tools:
go get github.com/driusan/de
```

Then as long as $GOPATH/bin is in your path, you can launch with `de [filename]`

13 changes: 13 additions & 0 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,19 @@ If the word is an internal editor command, it will perform that command. Otherwi
executed as a shell command and the output to stdout from that command will replace the currently
selected text.

The top of the window has a tag line that you can use as a scratch space for writing commands
without affecting the content of the current file. Typing in the tag line always acts like it's
in Insert mode, with the exception that the return key works as if it were in Normal mode and
either opens or executes the current word or selected text. You can access the tagline either by pointing
to it (focus follows pointer, you don't need to click) or typing the ; key. ; will just give focus
to the tag line, and : will give it focus as well as moving the cursor to the end and appending a
space. This means that you can type, for instance, :Save<Enter> to save the current file.

Note to Plan 9 users: you can *not* change the filename by editing it in the tagline and putting. The
filename there is only for reference, and updated when a new file is opened if the current filename
happens to be a prefix. There is currently no way to change the filename and save the file to another
name.

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.
Expand Down
45 changes: 33 additions & 12 deletions actions/execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,36 @@ func PerformAction(From, To demodel.Position, buff *demodel.CharBuffer) {
runOrExec(cmd, buff)
}

func PerformTagAction(From, To demodel.Position, buff *demodel.CharBuffer) {
if buff == nil || buff.Tagline == nil {
return
}
dot := demodel.Dot{}
// From and To should be the tag variant of Position functions, so run
// it directly on buff.
i, err := From(*buff)
if err != nil {
return
}

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

if l := uint(len(buff.Tagline.Buffer)); dot.Start >= l || dot.End+1 >= l {
// one last check for safety
return
}
cmd := string(buff.Tagline.Buffer[dot.Start : dot.End+1])

// now that the command has been extracted from the tagline, perform the command
// on the real buffer.
runOrExec(cmd, buff)
}

func OpenOrPerformAction(From, To demodel.Position, buff *demodel.CharBuffer) {
if buff == nil {
return
Expand All @@ -55,19 +85,10 @@ func OpenOrPerformAction(From, To demodel.Position, buff *demodel.CharBuffer) {
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
if err := OpenFile(cmd, buff); err == nil {
return
}

runOrExec(cmd, buff)
}

Expand All @@ -80,7 +101,7 @@ func runOrExec(cmd string, buff *demodel.CharBuffer) {
}

// it wasn't an internal command, so run the shell command
gocmd := exec.Command(cmd)
gocmd := exec.Command("sh", "-c", cmd)
stdout, err := gocmd.StdoutPipe()
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
Expand Down
52 changes: 37 additions & 15 deletions actions/find.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package actions

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

// Changes Dot to next instance of the character sequence between From
Expand Down Expand Up @@ -57,18 +54,7 @@ func FindNextOrOpen(From, To demodel.Position, buff *demodel.CharBuffer) {

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
if err := OpenFile(word, buff); err == nil {
return
}

Expand All @@ -82,3 +68,39 @@ func FindNextOrOpen(From, To demodel.Position, buff *demodel.CharBuffer) {
}
}
}

func FindNextOrOpenTag(From, To demodel.Position, buff *demodel.CharBuffer) {
if buff == nil || buff.Tagline == 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

// find the word between From and To in the tagline
word := string(buff.Tagline.Buffer[dot.Start:dot.End])

if err := OpenFile(word, buff); err == nil {
return
}

// the file doesn't exist, so find the next instance of word inside
// the *non-tag* buffer.
lenword := dot.End - dot.Start
for i := buff.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
}
}
}
76 changes: 76 additions & 0 deletions actions/open.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package actions

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

func OpenFile(filename string, buff *demodel.CharBuffer) error {
fstat, err := os.Stat(filename)
if err != nil {
return err
}
// file exists, so open it.
switch fstat.IsDir() {
case false:
// it's a file

b, ferr := ioutil.ReadFile(filename)
if ferr != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
return ferr
}

oldFilename := buff.Filename
buff.Buffer = b
buff.Filename = filename
buff.Dot.Start = 0
buff.Dot.End = 0

if buff.Tagline.Buffer == nil {
buff.Tagline.Buffer = make([]byte, 0)
}
// if the tagline starts with the filename, update it, otherwise,
// add it as a prefix.
buff.Tagline.Buffer = append(
[]byte(filename+" "),
bytes.TrimPrefix(buff.Tagline.Buffer, []byte(oldFilename))...,
)
case true:
// it's a directory
files, err := ioutil.ReadDir(filename)
if err != nil {
return err
}
os.Chdir(filename)

var bBuff bytes.Buffer
fmt.Fprintf(&bBuff, "./\n../\n")

for _, f := range files {

if f.IsDir() {
fmt.Fprintf(&bBuff, "%s/\n", f.Name())
} else {
fmt.Fprintf(&bBuff, "%s\n", f.Name())
}
}

oldFilename := buff.Filename

buff.Buffer = bBuff.Bytes()
buff.Filename = filename
buff.Dot.Start = 0
buff.Dot.End = 0

buff.Tagline.Buffer = append(
[]byte(filename+" "),
bytes.TrimPrefix(buff.Tagline.Buffer, []byte(oldFilename))...,
)

}
return nil
}
24 changes: 24 additions & 0 deletions demodel/charbuffer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package demodel

import (
"errors"
)

var NoTagline = errors.New("No tagline exists for buffer")

func (c *CharBuffer) AppendTag(val string) error {
if c == nil || c.Tagline == nil {
return NoTagline
}

c.Tagline.Buffer = append(c.Tagline.Buffer, []byte(val)...)
return nil
}

func (c *CharBuffer) ResetTagline() error {
c.Tagline = &CharBuffer{Buffer: []byte(c.Filename)}
c.AppendTag(" | Save Discard Cut Copy Paste Exit")
c.Tagline.Dot.Start = uint(len(c.Tagline.Buffer))
c.Tagline.Dot.End = c.Tagline.Dot.Start
return nil
}
4 changes: 4 additions & 0 deletions demodel/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ type CharBuffer struct {
// Each CharBuffer only has one SnarfBuffer associated with it, and it's not
// a tree.
SnarfBuffer []byte

// The tagline to display with this buffer. May be nil (for instance, taglines
// are CharBuffers, but taglines don't have their own tagline..)
Tagline *CharBuffer
}

// An action performs some sort of action to a character buffer,
Expand Down
2 changes: 2 additions & 0 deletions kbmap/deletemode.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ func deleteMap(e key.Event, buff *demodel.CharBuffer) (Map, error) {
actions.MoveCursor(position.DotStart, position.NextWordStart, buff)
}
actions.DeleteCursor(position.DotStart, position.DotEnd, buff)
return NormalMode, nil
case key.CodeB:
if Repeat == 0 {
Repeat = 1
Expand All @@ -83,6 +84,7 @@ func deleteMap(e key.Event, buff *demodel.CharBuffer) (Map, error) {
actions.MoveCursor(position.PrevWordStart, position.DotEnd, buff)
}
actions.DeleteCursor(position.DotStart, position.DotEnd, buff)
return NormalMode, nil
case key.CodeRightArrow:
return DeleteMode, ScrollRight
case key.CodeLeftArrow:
Expand Down
3 changes: 3 additions & 0 deletions kbmap/kbmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const (
NormalMode = defaultMaps(iota)
InsertMode
DeleteMode
TagMode
)

func (m defaultMaps) HandleKey(e key.Event, buff *demodel.CharBuffer) (Map, error) {
Expand All @@ -36,6 +37,8 @@ func (m defaultMaps) HandleKey(e key.Event, buff *demodel.CharBuffer) (Map, erro
return insertMap(e, buff)
case DeleteMode:
return deleteMap(e, buff)
case TagMode:
return tagMap(e, buff)
}
return nil, Invalid

Expand Down
10 changes: 10 additions & 0 deletions kbmap/normalmode.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,16 @@ func normalMap(e key.Event, buff *demodel.CharBuffer) (Map, error) {
actions.OpenOrPerformAction(position.DotStart, position.DotEnd, buff)
}
return NormalMode, nil
case key.CodeSemicolon:
if e.Modifiers&key.ModShift != 0 {
if buff.Tagline != nil {
buff.Tagline.Buffer = append(buff.Tagline.Buffer, ' ')

buff.Tagline.Dot.Start = uint(len(buff.Tagline.Buffer))
buff.Tagline.Dot.End = buff.Tagline.Dot.Start
}
}
return TagMode, nil
}
return NormalMode, Invalid
}
Loading

0 comments on commit 3b53da8

Please sign in to comment.