akiyosi.goneovim/editor/editor.go

527 lines
12 KiB
Go
Raw Normal View History

2017-11-09 02:11:05 +00:00
package editor
2017-03-14 01:52:44 +00:00
import (
"fmt"
"os"
2017-05-12 04:07:54 +01:00
"runtime"
2017-03-14 07:20:31 +00:00
"strconv"
"strings"
2017-06-02 10:41:28 +01:00
"sync"
2017-03-14 01:52:44 +00:00
2017-11-09 02:35:37 +00:00
"github.com/dzhou121/gonvim/fuzzy"
2017-03-14 01:52:44 +00:00
"github.com/neovim/go-client/nvim"
2017-06-16 08:43:05 +01:00
"github.com/therecipe/qt/core"
2017-06-06 02:42:11 +01:00
"github.com/therecipe/qt/widgets"
2017-03-14 01:52:44 +00:00
)
var editor *Editor
// Highlight is
type Highlight struct {
foreground *RGBA
background *RGBA
}
// Char is
type Char struct {
2017-06-16 16:28:31 +01:00
normalWidth bool
char string
highlight Highlight
2017-03-14 01:52:44 +00:00
}
// Editor is the editor
type Editor struct {
2017-06-20 03:32:19 +01:00
app *widgets.QApplication
2017-06-12 09:41:03 +01:00
nvim *nvim.Nvim
window *widgets.QMainWindow
2017-06-12 09:41:03 +01:00
nvimAttached bool
mode string
font *Font
smallerFont *Font
rows int
cols int
cursorNew *Cursor
Foreground *RGBA
Background *RGBA
special *RGBA
screen *Screen
2017-05-10 07:28:44 +01:00
close chan bool
2017-06-12 09:15:08 +01:00
loc *Locpopup
2017-06-12 10:10:48 +01:00
signature *Signature
2017-05-10 07:28:44 +01:00
popup *PopupMenu
finder *Finder
2017-06-28 11:06:47 +01:00
cmdline *Cmdline
2017-06-28 08:28:24 +01:00
palette *Palette
2017-05-10 07:28:44 +01:00
tabline *Tabline
statusline *Statusline
2017-11-09 01:41:26 +00:00
message *Message
drawStatusline bool
2017-11-08 20:46:08 +01:00
drawTabline bool
drawLint bool
2017-05-10 07:28:44 +01:00
statuslineHeight int
width int
height int
tablineHeight int
selectedBg *RGBA
matchFg *RGBA
2017-06-02 10:41:28 +01:00
resizeMutex sync.Mutex
2017-06-16 08:43:05 +01:00
signal *editorSignal
redrawUpdates chan [][]interface{}
guiUpdates chan []interface{}
}
type editorSignal struct {
core.QObject
_ func() `signal:"redrawSignal"`
_ func() `signal:"guiSignal"`
_ func() `signal:"statuslineSignal"`
_ func() `signal:"locpopupSignal"`
_ func() `signal:"lintSignal"`
_ func() `signal:"gitSignal"`
2017-11-09 01:41:26 +00:00
_ func() `signal:"messageSignal"`
2017-03-14 01:52:44 +00:00
}
2017-03-14 07:20:31 +00:00
func (e *Editor) handleNotification() {
e.nvim.RegisterHandler("Gui", func(updates ...interface{}) {
2017-06-16 08:43:05 +01:00
e.guiUpdates <- updates
e.signal.GuiSignal()
2017-03-14 07:20:31 +00:00
})
2017-03-14 01:52:44 +00:00
e.nvim.RegisterHandler("redraw", func(updates ...[]interface{}) {
2017-06-16 08:43:05 +01:00
e.redrawUpdates <- updates
e.signal.RedrawSignal()
2017-05-11 06:18:54 +01:00
})
}
2017-06-16 08:43:05 +01:00
func (e *Editor) handleRPCGui(updates []interface{}) {
2017-05-11 06:18:54 +01:00
event := updates[0].(string)
switch event {
case "Font":
e.guiFont(updates[1:])
case "Linespace":
e.guiLinespace(updates[1:])
case "finder_pattern":
e.finder.showPattern(updates[1:])
case "finder_pattern_pos":
e.finder.cursorPos(updates[1:])
case "finder_show_result":
e.finder.showResult(updates[1:])
case "finder_hide":
e.finder.hide()
case "finder_select":
e.finder.selectResult(updates[1:])
case "signature_show":
2017-06-15 15:54:27 +01:00
e.signature.showItem(updates[1:])
2017-05-11 06:18:54 +01:00
case "signature_pos":
2017-06-12 10:10:48 +01:00
e.signature.pos(updates[1:])
2017-05-11 06:18:54 +01:00
case "signature_hide":
2017-06-12 10:10:48 +01:00
e.signature.hide()
2017-05-11 06:18:54 +01:00
default:
fmt.Println("unhandled Gui event", event)
}
}
2017-06-16 08:43:05 +01:00
func (e *Editor) handleRedraw(updates [][]interface{}) {
s := e.screen
2017-05-11 06:18:54 +01:00
for _, update := range updates {
event := update[0].(string)
args := update[1:]
switch event {
case "update_fg":
args := update[1].([]interface{})
2017-05-25 06:17:31 +01:00
color := reflectToInt(args[0])
if color == -1 {
2017-06-02 10:53:27 +01:00
editor.Foreground = newRGBA(255, 255, 255, 1)
2017-05-25 06:17:31 +01:00
} else {
editor.Foreground = calcColor(reflectToInt(args[0]))
}
2017-05-11 06:18:54 +01:00
case "update_bg":
args := update[1].([]interface{})
2017-06-16 08:43:05 +01:00
s.updateBg(args)
case "update_sp":
args := update[1].([]interface{})
color := reflectToInt(args[0])
if color == -1 {
editor.special = newRGBA(255, 255, 255, 1)
} else {
editor.special = calcColor(reflectToInt(args[0]))
}
2017-05-11 06:18:54 +01:00
case "cursor_goto":
2017-06-16 08:43:05 +01:00
s.cursorGoto(args)
2017-05-11 06:18:54 +01:00
case "put":
2017-06-16 08:43:05 +01:00
s.put(args)
2017-05-11 06:18:54 +01:00
case "eol_clear":
2017-06-16 08:43:05 +01:00
s.eolClear(args)
2017-05-11 06:18:54 +01:00
case "clear":
2017-06-16 08:43:05 +01:00
s.clear(args)
2017-05-11 06:18:54 +01:00
case "resize":
2017-06-16 08:43:05 +01:00
s.resize(args)
2017-05-11 06:18:54 +01:00
case "highlight_set":
2017-06-16 08:43:05 +01:00
s.highlightSet(args)
2017-05-11 06:18:54 +01:00
case "set_scroll_region":
2017-06-16 08:43:05 +01:00
s.setScrollRegion(args)
2017-05-11 06:18:54 +01:00
case "scroll":
2017-06-16 08:43:05 +01:00
s.scroll(args)
2017-05-11 06:18:54 +01:00
case "mode_change":
2017-05-16 07:16:53 +01:00
arg := update[len(update)-1].([]interface{})
2017-05-11 06:18:54 +01:00
editor.mode = arg[0].(string)
case "popupmenu_show":
2017-06-15 15:54:27 +01:00
editor.popup.showItems(args)
2017-05-11 06:18:54 +01:00
case "popupmenu_hide":
2017-06-15 15:54:27 +01:00
editor.popup.hide()
2017-05-11 06:18:54 +01:00
case "popupmenu_select":
editor.popup.selectItem(args)
case "tabline_update":
editor.tabline.update(args)
2017-06-28 11:06:47 +01:00
case "cmdline_show":
editor.cmdline.show(args)
case "cmdline_pos":
editor.cmdline.changePos(args)
case "cmdline_char":
editor.cmdline.putChar(args)
case "cmdline_hide":
editor.cmdline.hide(args)
case "cmdline_function_show":
editor.cmdline.functionShow()
case "cmdline_function_hide":
editor.cmdline.functionHide()
2017-06-28 15:52:36 +01:00
case "wildmenu_show":
editor.cmdline.wildmenuShow(args)
case "wildmenu_select":
editor.cmdline.wildmenuSelect(args)
case "wildmenu_hide":
editor.cmdline.wildmenuHide()
2017-11-09 01:41:26 +00:00
case "msg_start_kind":
if len(args) > 0 {
kinds, ok := args[len(args)-1].([]interface{})
if ok {
if len(kinds) > 0 {
kind, ok := kinds[len(kinds)-1].(string)
if ok {
editor.message.kind = kind
}
}
}
}
case "msg_chunk":
editor.message.chunk(args)
case "msg_end":
case "msg_showcmd":
case "messages":
case "busy_start":
case "busy_stop":
2017-05-11 06:18:54 +01:00
default:
fmt.Println("Unhandle event", event)
2017-03-14 01:52:44 +00:00
}
2017-05-11 06:18:54 +01:00
}
2017-06-30 15:56:11 +01:00
s.update()
2017-06-07 09:18:21 +01:00
editor.cursorNew.update()
2017-06-12 09:15:08 +01:00
editor.statusline.mode.redraw()
2017-03-14 01:52:44 +00:00
}
2017-03-14 07:20:31 +00:00
func (e *Editor) guiFont(args ...interface{}) {
fontArg := args[0].([]interface{})
parts := strings.Split(fontArg[0].(string), ":")
if len(parts) < 1 {
return
}
height := 14
for _, p := range parts[1:] {
if strings.HasPrefix(p, "h") {
var err error
height, err = strconv.Atoi(p[1:])
if err != nil {
return
}
}
}
e.font.change(parts[0], height)
2017-06-13 16:15:05 +01:00
e.nvimResize()
2017-06-07 11:31:10 +01:00
e.popup.updateFont(e.font)
2017-03-14 07:20:31 +00:00
}
func (e *Editor) guiLinespace(args ...interface{}) {
fontArg := args[0].([]interface{})
var lineSpace int
var err error
switch arg := fontArg[0].(type) {
case string:
lineSpace, err = strconv.Atoi(arg)
if err != nil {
return
}
case int32: // can't combine these in a type switch without compile error
lineSpace = int(arg)
case int64:
lineSpace = int(arg)
default:
2017-03-14 07:20:31 +00:00
return
}
e.font.changeLineSpace(lineSpace)
2017-06-13 16:15:05 +01:00
e.nvimResize()
2017-03-14 07:20:31 +00:00
}
2017-06-02 10:41:28 +01:00
func (e *Editor) nvimResize() {
2017-06-13 16:15:05 +01:00
// e.screen.paintMutex.Lock()
// defer e.screen.paintMutex.Unlock()
2017-06-30 11:43:06 +01:00
width, height := e.screen.width, e.screen.height
2017-11-09 01:41:26 +00:00
height += e.tabline.marginTop - e.tabline.marginDefault + e.tabline.marginBottom - e.tabline.marginDefault
2017-05-12 04:55:21 +01:00
cols := int(float64(width) / editor.font.truewidth)
2017-03-14 07:20:31 +00:00
rows := height / editor.font.lineHeight
oldCols := editor.cols
oldRows := editor.rows
2017-03-14 07:26:11 +00:00
editor.cols = cols
editor.rows = rows
2017-11-09 01:41:26 +00:00
remainingHeight := height - rows*editor.font.lineHeight
remainingHeightBottom := remainingHeight / 2
remainingHeightTop := remainingHeight - remainingHeightBottom
e.tabline.marginTop = e.tabline.marginDefault + remainingHeightTop
e.tabline.marginBottom = e.tabline.marginDefault + remainingHeightBottom
e.tabline.updateMargin()
2017-03-14 07:26:11 +00:00
if oldCols > 0 && oldRows > 0 {
2017-05-31 08:57:12 +01:00
if cols != oldCols || rows != oldRows {
editor.nvim.TryResizeUI(cols, rows)
}
2017-03-14 07:20:31 +00:00
}
}
2017-03-14 01:52:44 +00:00
func (hl *Highlight) copy() Highlight {
highlight := Highlight{}
if hl.foreground != nil {
highlight.foreground = hl.foreground.copy()
}
if hl.background != nil {
highlight.background = hl.background.copy()
}
return highlight
}
2017-06-06 02:42:11 +01:00
2017-06-23 07:14:08 +01:00
func (e *Editor) configure() {
var drawSplit interface{}
e.nvim.Var("gonvim_draw_split", &drawSplit)
if isZero(drawSplit) {
e.screen.drawSplit = false
} else {
e.screen.drawSplit = true
}
var drawStatusline interface{}
e.nvim.Var("gonvim_draw_statusline", &drawStatusline)
if isZero(drawStatusline) {
e.drawStatusline = false
} else {
e.drawStatusline = true
}
2017-11-08 20:46:08 +01:00
var drawTabline interface{}
e.nvim.Var("gonvim_draw_tabline", &drawTabline)
if isZero(drawTabline) {
e.drawTabline = false
} else {
e.drawTabline = true
}
2017-06-23 07:14:08 +01:00
var drawLint interface{}
e.nvim.Var("gonvim_draw_lint", &drawLint)
if isZero(drawLint) {
e.drawLint = false
} else {
e.drawLint = true
}
var startFullscreen interface{}
e.nvim.Var("gonvim_start_fullscreen", &startFullscreen)
if isTrue(startFullscreen) {
e.window.ShowFullScreen()
}
2017-06-23 07:14:08 +01:00
}
2017-11-09 02:11:05 +00:00
// InitEditor is
func InitEditor() {
2017-06-07 09:18:21 +01:00
app := widgets.NewQApplication(0, nil)
2017-06-13 16:15:05 +01:00
devicePixelRatio := app.DevicePixelRatio()
2017-06-06 02:42:11 +01:00
fontFamily := ""
switch runtime.GOOS {
case "windows":
fontFamily = "Consolas"
case "darwin":
fontFamily = "Courier New"
default:
fontFamily = "Monospace"
}
font := initFontNew(fontFamily, 14, 6)
width := 800
height := 600
//create a window
window := widgets.NewQMainWindow(nil, 0)
2017-06-06 07:21:17 +01:00
window.SetWindowTitle("Gonvim")
2017-06-06 02:42:11 +01:00
window.SetContentsMargins(0, 0, 0, 0)
window.SetMinimumSize2(width, height)
2017-06-09 11:32:32 +01:00
tabline := initTablineNew()
statusline := initStatuslineNew()
2017-06-13 16:15:05 +01:00
screen := initScreenNew(devicePixelRatio)
2017-06-07 09:18:21 +01:00
cursor := initCursorNew()
cursor.widget.SetParent(screen.widget)
2017-06-07 11:31:10 +01:00
popup := initPopupmenuNew(font)
2017-06-07 11:24:42 +01:00
popup.widget.SetParent(screen.widget)
2017-06-08 11:46:09 +01:00
finder := initFinder()
2017-06-28 08:28:24 +01:00
palette := initPalette()
palette.widget.SetParent(screen.widget)
2017-06-12 09:15:08 +01:00
loc := initLocpopup()
loc.widget.SetParent(screen.widget)
2017-06-12 10:10:48 +01:00
signature := initSignature()
signature.widget.SetParent(screen.widget)
2017-11-09 01:41:26 +00:00
message := initMessage()
message.widget.SetParent(screen.widget)
2017-06-06 07:21:17 +01:00
window.ConnectKeyPressEvent(screen.keyPress)
2017-06-06 02:42:11 +01:00
layout := widgets.NewQVBoxLayout()
widget := widgets.NewQWidget(nil, 0)
widget.SetContentsMargins(0, 0, 0, 0)
widget.SetLayout(layout)
2017-06-09 11:32:32 +01:00
layout.AddWidget(tabline.widget, 0, 0)
2017-06-06 02:42:11 +01:00
layout.AddWidget(screen.widget, 1, 0)
2017-06-09 11:32:32 +01:00
layout.AddWidget(statusline.widget, 0, 0)
2017-06-06 02:42:11 +01:00
layout.SetContentsMargins(0, 0, 0, 0)
2017-06-07 09:18:21 +01:00
layout.SetSpacing(0)
2017-06-06 02:42:11 +01:00
window.SetCentralWidget(widget)
neovim, err := nvim.NewEmbedded(&nvim.EmbedOptions{
Args: os.Args[1:],
})
if err != nil {
fmt.Println("nvim start error", err)
2017-06-07 09:18:21 +01:00
app.Quit()
2017-06-06 02:42:11 +01:00
return
}
2017-06-16 08:43:05 +01:00
signal := NewEditorSignal(nil)
2017-06-06 02:42:11 +01:00
editor = &Editor{
2017-06-20 03:32:19 +01:00
app: app,
2017-06-16 08:43:05 +01:00
nvim: neovim,
window: window,
2017-06-16 08:43:05 +01:00
nvimAttached: false,
screen: screen,
cursorNew: cursor,
mode: "normal",
close: make(chan bool),
popup: popup,
finder: finder,
2017-06-28 11:06:47 +01:00
cmdline: initCmdline(),
2017-06-28 08:28:24 +01:00
palette: palette,
2017-06-16 08:43:05 +01:00
loc: loc,
signature: signature,
tabline: tabline,
2017-11-09 01:41:26 +00:00
message: message,
2017-06-16 08:43:05 +01:00
width: width,
height: height,
statusline: statusline,
font: font,
selectedBg: newRGBA(81, 154, 186, 0.5),
matchFg: newRGBA(81, 154, 186, 1),
signal: signal,
redrawUpdates: make(chan [][]interface{}, 1000),
guiUpdates: make(chan []interface{}, 1000),
2017-06-06 02:42:11 +01:00
}
2017-06-16 08:43:05 +01:00
signal.ConnectRedrawSignal(func() {
updates := <-editor.redrawUpdates
editor.handleRedraw(updates)
})
signal.ConnectGuiSignal(func() {
updates := <-editor.guiUpdates
editor.handleRPCGui(updates)
})
2017-06-06 02:42:11 +01:00
editor.handleNotification()
// editor.finder.rePosition()
go func() {
err := neovim.Serve()
if err != nil {
fmt.Println(err)
}
editor.close <- true
}()
2017-06-13 16:15:05 +01:00
screen.updateSize()
2017-07-03 07:14:29 +01:00
apiInfo, err := editor.nvim.APIInfo()
if err != nil {
fmt.Println("nvim get API info error", err)
app.Quit()
return
}
2017-11-08 20:46:08 +01:00
editor.configure()
2017-06-06 02:42:11 +01:00
o := make(map[string]interface{})
o["rgb"] = true
o["ext_popupmenu"] = true
2017-11-08 20:46:08 +01:00
o["ext_tabline"] = editor.drawTabline
2017-07-03 07:14:29 +01:00
for _, item := range apiInfo {
i, ok := item.(map[string]interface{})
if !ok {
continue
}
for k, v := range i {
if k != "ui_events" {
continue
}
events, ok := v.([]interface{})
if !ok {
continue
}
for _, event := range events {
function, ok := event.(map[string]interface{})
if !ok {
continue
}
name, ok := function["name"]
if !ok {
continue
}
if name == "wildmenu_show" {
o["ext_wildmenu"] = true
} else if name == "cmdline_show" {
o["ext_cmdline"] = true
2017-11-09 01:41:26 +00:00
} else if name == "msg_chunk" {
o["ext_messages"] = true
2017-07-03 07:14:29 +01:00
}
}
}
}
2017-06-06 02:42:11 +01:00
err = editor.nvim.AttachUI(editor.cols, editor.rows, o)
if err != nil {
fmt.Println("nvim attach UI error", err)
2017-06-07 09:18:21 +01:00
app.Quit()
2017-06-06 02:42:11 +01:00
return
}
editor.nvim.Subscribe("Gui")
editor.nvim.Command("runtime plugin/nvim_gui_shim.vim")
editor.nvim.Command("runtime! ginit.vim")
editor.nvim.Command("let g:gonvim_running=1")
2017-11-09 02:35:37 +00:00
fuzzy.RegisterPlugin(editor.nvim)
2017-11-08 20:46:08 +01:00
tabline.subscribe()
2017-06-12 09:41:03 +01:00
statusline.subscribe()
loc.subscribe()
2017-11-09 01:41:26 +00:00
message.subscribe()
2017-06-06 02:42:11 +01:00
go func() {
<-editor.close
2017-06-07 09:18:21 +01:00
app.Quit()
2017-06-06 02:42:11 +01:00
}()
window.Show()
2017-06-16 10:35:53 +01:00
popup.widget.Hide()
2017-11-09 01:41:26 +00:00
palette.hide()
2017-06-16 10:35:53 +01:00
loc.widget.Hide()
signature.widget.Hide()
2017-06-06 02:42:11 +01:00
widgets.QApplication_Exec()
}