Skip to content

Commit

Permalink
Added plugin support and documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
driusan committed May 17, 2016
1 parent e518b4d commit b475636
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 47 deletions.
22 changes: 18 additions & 4 deletions PLUGINS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
There are two types of plugins that can you might want to add to de: things that add new
commands, and things that change the rendering (ie to add syntax highlighting.)

Currently only the former is possible, though I intend to add support for the latter.
(Go syntax highlighting is built in, because de is written in Go. It will be refactored
into a plugin as a test case.)
## Command Plugins

The easiest way to write a "plugin" for de is to just to write a shell command that can be
executed with either a middle click or the enter key to insert into the current buffer,
Expand All @@ -28,4 +26,20 @@ The callback function should take two parameters:
2. A *demodel.CharBuffer which contains the current character buffer, snarf buffer, and dot.
See the godoc of the demodel package for more details. You can manipulate the CharBuffer.Buffer
slice however you want, but the positions package has helpers to calculate positions relative
to the cursor/selections, and the actions package has helpers to perform common manipulations.
to the cursor/selections, and the actions package has helpers to perform common manipulations.

## Rendering Plugins

A rendering plugin changes the way that a character buffer is rendered to the screen. You can
create a renderer by implementing the github.com/driusan/de/renderer/Renderer interface and
registering it with renderer.RegisterRenderer in your init function, then adding it to plugins.go
and recompiling similarly to command plugins.

The Renderer interface requires two functions: a CanRender(demodel.CharBuffer) method which
determines whether de should use your renderer to attempt to render a character buffer or skip
to the next available renderer, and a Render(demodel.CharBuffer) method which returns an image.Image
to be rendered to the screen representing that character buffer, and a renderer.ImageMap which can
be used to convert points in that image into indexes into CharBuffer.Buffer for the mouse to use.

See renderer/gorenderer/gosyntax.go for a sample renderer that supports syntax highlighting for
the Go language.
9 changes: 2 additions & 7 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"image/draw"
"io/ioutil"
"os"
"strings"
)

var viewport struct {
Expand Down Expand Up @@ -91,13 +90,9 @@ func main() {
var lastKeyboardMode kbmap.Map = kbmap.NormalMode
viewport.KeyboardMode = kbmap.NormalMode

var render renderer.Renderer
if strings.HasSuffix(filename, ".go") {
render = &renderer.GoSyntax{}
} else {
render = &renderer.NoSyntaxRenderer{}
}
render := renderer.GetRenderer(buff)
img, imap, err := render.Render(buff)

if err != nil {
panic(err)
}
Expand Down
4 changes: 4 additions & 0 deletions plugins.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package main

import (
// Default command plugins
_ "github.com/driusan/de/actions/defaults"

// Go syntax highlighting plugin
_ "github.com/driusan/de/renderer/gorenderer"
)
9 changes: 8 additions & 1 deletion position/positions.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,16 @@ func NextLine(buff demodel.CharBuffer) (uint, error) {
}

func EndOfLine(buff demodel.CharBuffer) (uint, error) {
if len(buff.Buffer) == 0 {
return 0, Invalid
}

if buff.Buffer[buff.Dot.End] == '\n' {
return buff.Dot.End, nil
}
for i := buff.Dot.End; i < uint(len(buff.Buffer)); i++ {
if buff.Buffer[i] == '\n' {
return i, nil
return i - 1, nil
}
}
return uint(len(buff.Buffer) - 1), nil
Expand Down
59 changes: 33 additions & 26 deletions renderer/gosyntax.go → renderer/gorenderer/gosyntax.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,28 @@ import (
"github.com/driusan/de/demodel"
"unicode"
//"fmt"
"github.com/driusan/de/renderer"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
"image"
// "image/color"
"image/draw"
"strings"
)

type GoSyntax struct{}

func (rd *GoSyntax) CanRender(buf demodel.CharBuffer) bool {
return strings.HasSuffix(buf.Filename, ".go")
}
func (rd *GoSyntax) calcImageSize(buf demodel.CharBuffer) image.Rectangle {
metrics := MonoFontFace.Metrics()
metrics := renderer.MonoFontFace.Metrics()
runes := bytes.Runes(buf.Buffer)
_, MglyphWidth, _ := MonoFontFace.GlyphBounds('M')
_, MglyphWidth, _ := renderer.MonoFontFace.GlyphBounds('M')
rt := image.ZR
var lineSize fixed.Int26_6
for _, r := range runes {
_, glyphWidth, _ := MonoFontFace.GlyphBounds(r)
_, glyphWidth, _ := renderer.MonoFontFace.GlyphBounds(r)
switch r {
case '\t':
lineSize += MglyphWidth * 8
Expand All @@ -39,76 +44,78 @@ func (rd *GoSyntax) calcImageSize(buf demodel.CharBuffer) image.Rectangle {
return rt
}

func (rd *GoSyntax) Render(buf demodel.CharBuffer) (image.Image, ImageMap, error) {
func (rd *GoSyntax) Render(buf demodel.CharBuffer) (image.Image, renderer.ImageMap, error) {
dstSize := rd.calcImageSize(buf)
dst := image.NewRGBA(dstSize)
metrics := MonoFontFace.Metrics()
metrics := renderer.MonoFontFace.Metrics()
writer := font.Drawer{
Dst: dst,
Src: &image.Uniform{TextColour},
Src: &image.Uniform{renderer.TextColour},
Dot: fixed.P(0, metrics.Ascent.Floor()),
Face: MonoFontFace,
Face: renderer.MonoFontFace,
}
runes := bytes.Runes(buf.Buffer)

// it's a monospace font, so only do this once outside of the for loop..
// use an M so that space characters are based on an em-quad if we change
// to a non-monospace font.
//writer.Src = &image.Uniform{TextColour}
im := make(ImageMap, 0)
im := make(renderer.ImageMap, 0)

var inLineComment, inMultilineComment, inString, inCharString bool

_, MglyphWidth, _ := MonoFontFace.GlyphBounds('M')
// Used for calculating the size of a tab.
_, MglyphWidth, _ := renderer.MonoFontFace.GlyphBounds('M')

// Some characters (like a terminating quote) only change the active colour
//after being rendered.
var nextColor image.Image
for i, r := range runes {
_, glyphWidth, _ := MonoFontFace.GlyphBounds(r)
// Do this inside the loop anyways, in case someone changes it to a
// variable width font..
_, glyphWidth, _ := renderer.MonoFontFace.GlyphBounds(r)
switch r {
case '\n':
if inLineComment && !inMultilineComment && !inString {
inLineComment = false
writer.Src = &image.Uniform{TextColour}
writer.Src = &image.Uniform{renderer.TextColour}
}
case '\'':
if !IsEscaped(i, runes) {
if inCharString {
// end of a string, colourize the quote too.
nextColor = &image.Uniform{TextColour}
nextColor = &image.Uniform{renderer.TextColour}
inCharString = false
} else if !inLineComment && !inMultilineComment && !inString {
inCharString = true
writer.Src = &image.Uniform{StringColour}
writer.Src = &image.Uniform{renderer.StringColour}
}
}
case '"':
if !IsEscaped(i, runes) {
if inString {
inString = false
nextColor = &image.Uniform{TextColour}
nextColor = &image.Uniform{renderer.TextColour}
} else if !inLineComment && !inMultilineComment && !inCharString {
inString = true
writer.Src = &image.Uniform{StringColour}
writer.Src = &image.Uniform{renderer.StringColour}
}
}
case '/':
if string(runes[i:i+2]) == "//" {
if !inCharString && !inMultilineComment && !inString {
inLineComment = true
writer.Src = &image.Uniform{CommentColour}
writer.Src = &image.Uniform{renderer.CommentColour}
}
}
case ' ', '\t':
if !inCharString && !inMultilineComment && !inString && !inLineComment {
writer.Src = &image.Uniform{TextColour}
writer.Src = &image.Uniform{renderer.TextColour}
}
default:
if !inCharString && !inMultilineComment && !inString && !inLineComment {
if IsLanguageKeyword(i, runes) {
writer.Src = &image.Uniform{KeywordColour}
writer.Src = &image.Uniform{renderer.KeywordColour}
} else if IsLanguageType(i, runes) {
writer.Src = &image.Uniform{BuiltinTypeColour}
writer.Src = &image.Uniform{renderer.BuiltinTypeColour}
} else if StartsLanguageDeliminator(r) {
writer.Src = &image.Uniform{TextColour}
writer.Src = &image.Uniform{renderer.TextColour}
}
}
}
Expand All @@ -126,13 +133,13 @@ func (rd *GoSyntax) Render(buf demodel.CharBuffer) (image.Image, ImageMap, error
}
runeRectangle.Max.Y = runeRectangle.Min.Y + metrics.Height.Ceil() + 1

im = append(im, ImageLoc{runeRectangle, uint(i)})
im = append(im, renderer.ImageLoc{runeRectangle, uint(i)})
if uint(i) >= buf.Dot.Start && uint(i) <= buf.Dot.End {
// it's in dot, so highlight the background
draw.Draw(
dst,
runeRectangle,
&image.Uniform{TextHighlight},
&image.Uniform{renderer.TextHighlight},
image.ZP,
draw.Src,
)
Expand Down
9 changes: 9 additions & 0 deletions renderer/gorenderer/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package renderer

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

func init() {
renderer.RegisterRenderer(&GoSyntax{})
}
9 changes: 0 additions & 9 deletions renderer/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ package renderer

import (
"fmt"
"github.com/driusan/de/demodel"
"github.com/driusan/fonts"
"github.com/golang/freetype/truetype"
"golang.org/x/image/font"
// "golang.org/x/image/font/basicfont"
"image"
"os"
)

Expand Down Expand Up @@ -39,10 +37,3 @@ func init() {
// it's not as pretty and doesn't have as many runes.
//MonoFontFace = basicfont.Face7x13
}

// Renders the character buffer to an image which can
// be displayed on a screen. For instance, to a shiny
// window.
type Renderer interface {
Render(demodel.CharBuffer) (image.Image, ImageMap, error)
}
5 changes: 5 additions & 0 deletions renderer/nosyntax.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"image/draw"
)

// The default renderer. Performs no syntax highlighting.
type NoSyntaxRenderer struct{}

func (r NoSyntaxRenderer) calcImageSize(buf demodel.CharBuffer) image.Rectangle {
Expand All @@ -37,6 +38,10 @@ func (r NoSyntaxRenderer) calcImageSize(buf demodel.CharBuffer) image.Rectangle
return rt
}

func (r NoSyntaxRenderer) CanRender(demodel.CharBuffer) bool {
return true
}

var NoCharacter error = errors.New("No character under the mouse cursor.")

type ImageLoc struct {
Expand Down
32 changes: 32 additions & 0 deletions renderer/register.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package renderer

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

type Renderer interface {
CanRender(demodel.CharBuffer) bool
Render(demodel.CharBuffer) (image.Image, ImageMap, error)
}

var renderers []Renderer

func init() {
// Make sure renderers is initialized with at least 1 renderer
// that can render anything.
renderers = []Renderer{NoSyntaxRenderer{}}

}
func RegisterRenderer(r Renderer) {
renderers = append(renderers, r)
}

func GetRenderer(buff demodel.CharBuffer) Renderer {
for i := len(renderers) - 1; i >= 0; i-- {
if renderers[i].CanRender(buff) {
return renderers[i]
}
}
return renderers[0]
}

0 comments on commit b475636

Please sign in to comment.