mirror of
https://github.com/akiyosi/goneovim.git
synced 2025-08-04 18:14:34 +02:00
1317 lines
35 KiB
Go
1317 lines
35 KiB
Go
package editor
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/akiyosi/goneovim/util"
|
|
frameless "github.com/akiyosi/goqtframelesswindow"
|
|
"github.com/akiyosi/qt/core"
|
|
"github.com/akiyosi/qt/gui"
|
|
"github.com/akiyosi/qt/widgets"
|
|
homedir "github.com/mitchellh/go-homedir"
|
|
"github.com/neovim/go-client/nvim"
|
|
)
|
|
|
|
var editor *Editor
|
|
|
|
const (
|
|
WORKSPACELEN = 10
|
|
NVIMCALLTIMEOUT = 320
|
|
NVIMCALLTIMEOUT2 = 45
|
|
)
|
|
|
|
type editorSignal struct {
|
|
core.QObject
|
|
_ func() `signal:"notifySignal"`
|
|
_ func() `signal:"sidebarSignal"`
|
|
_ func() `signal:"geometrySignal"`
|
|
}
|
|
|
|
// ColorPalette is
|
|
type ColorPalette struct {
|
|
e *Editor
|
|
|
|
fg *RGBA
|
|
bg *RGBA
|
|
inactiveFg *RGBA
|
|
comment *RGBA
|
|
abyss *RGBA
|
|
matchFg *RGBA
|
|
selectedBg *RGBA
|
|
sideBarFg *RGBA
|
|
sideBarBg *RGBA
|
|
sideBarSelectedItemBg *RGBA
|
|
scrollBarFg *RGBA
|
|
scrollBarBg *RGBA
|
|
widgetFg *RGBA
|
|
widgetBg *RGBA
|
|
widgetInputArea *RGBA
|
|
minimapCurrentRegion *RGBA
|
|
windowSeparator *RGBA
|
|
indentGuide *RGBA
|
|
}
|
|
|
|
// NotifyButton is
|
|
type NotifyButton struct {
|
|
action func()
|
|
text string
|
|
}
|
|
|
|
// Notify is
|
|
type Notify struct {
|
|
message string
|
|
buttons []*NotifyButton
|
|
level NotifyLevel
|
|
period int
|
|
}
|
|
|
|
type Options struct {
|
|
Geometry string `long:"geometry" description:"Initial window geometry [e.g. --geometry=800x600]"`
|
|
Server string `long:"server" description:"Remote session address [e.g. --server=host:3456]"`
|
|
Ssh string `long:"ssh" description:"Attaching to a remote nvim via ssh. Default port is 22. [e.g. --ssh=user@host:port]"`
|
|
Nvim string `long:"nvim" description:"Executable nvim path to attach [e.g. --nvim=/path/to/nvim]"`
|
|
Debug string `long:"debug" description:"Run debug mode with debug.log(default) file [e.g. --debug=/path/to/my-debug.log]" optional:"yes" optional-value:"debug.log"`
|
|
Fullscreen bool `long:"fullscreen" description:"Open the window in fullscreen on startup"`
|
|
Maximized bool `long:"maximized" description:"Maximize the window on startup"`
|
|
Exttabline bool `long:"exttabline" description:"Externalize the tabline"`
|
|
Extcmdline bool `long:"extcmdline" description:"Externalize the cmdline"`
|
|
Extmessages bool `long:"extmessages" description:"Externalize the messages. Sets --extcmdline implicitly"`
|
|
Extpopupmenu bool `long:"extpopupmenu" description:"Externalize the popupmenu"`
|
|
Version bool `long:"version" description:"Print Goneovim version"`
|
|
Wsl *string `long:"wsl" description:"Attach to nvim process in wsl environment with distribution(default) [e.g. --wsl=Ubuntu]" optional:"yes" optional-value:""`
|
|
Nofork bool `long:"nofork" description:"Run in foreground"`
|
|
NoConfig bool `long:"noconfig" description:"Run Goneovim with no config. (Equivalent to loading an empty settings.toml)"`
|
|
}
|
|
|
|
// Editor is the editor
|
|
type Editor struct {
|
|
stop chan int
|
|
signal *editorSignal
|
|
ctx context.Context
|
|
app *widgets.QApplication
|
|
widget *widgets.QWidget
|
|
splitter *widgets.QSplitter
|
|
window *frameless.QFramelessWindow
|
|
specialKeys map[core.Qt__Key]string
|
|
svgs map[string]*SvgXML
|
|
notifyStartPos *core.QPoint
|
|
colors *ColorPalette
|
|
notify chan *Notify
|
|
fontCh chan []*Font
|
|
cbChan chan *string
|
|
chUiPrepared chan bool
|
|
openingFileCh chan string
|
|
geometryUpdateTimer *time.Timer
|
|
sysTray *widgets.QSystemTrayIcon
|
|
side *WorkspaceSide
|
|
savedGeometry *core.QByteArray
|
|
prefixToMapMetaKey string
|
|
macAppArg string
|
|
configDir string
|
|
homeDir string
|
|
version string
|
|
config gonvimConfig
|
|
opts Options
|
|
font *Font
|
|
fallbackfonts []*Font
|
|
notifications []*Notification
|
|
workspaces []*Workspace
|
|
args []string
|
|
ppid int
|
|
keyControl core.Qt__Key
|
|
keyCmd core.Qt__Key
|
|
windowSize [2]int
|
|
width int
|
|
active int
|
|
startuptime int64
|
|
iconSize int
|
|
height int
|
|
notificationWidth int
|
|
stopOnce sync.Once
|
|
muMetaKey sync.Mutex
|
|
geometryUpdateMutex sync.RWMutex
|
|
doRestoreSessions bool
|
|
initialColumns int
|
|
initialLines int
|
|
isSetColumns bool
|
|
isSetLines bool
|
|
isSetGuiColor bool
|
|
isDisplayNotifications bool
|
|
isKeyAutoRepeating bool
|
|
isWindowResized bool
|
|
isWindowNowActivated bool
|
|
isWindowNowInactivated bool
|
|
isExtWinNowActivated bool
|
|
isExtWinNowInactivated bool
|
|
isHideMouse bool
|
|
isBindNvimSizeToAppwin bool
|
|
isUiPrepared bool
|
|
isWindowMaximizing bool
|
|
}
|
|
|
|
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()
|
|
}
|
|
highlight.bold = hl.bold
|
|
highlight.italic = hl.italic
|
|
|
|
return highlight
|
|
}
|
|
|
|
// InitEditor is
|
|
func InitEditor(options Options, args []string) {
|
|
|
|
// --------------------
|
|
// For profiling goneovim
|
|
// --------------------
|
|
//
|
|
// https://blog.golang.org/pprof
|
|
// After running the app, exec the following:
|
|
// $ go tool pprof -http=localhost:9090 cpuprofile
|
|
//
|
|
// Comment out the following::
|
|
|
|
// // * built-in net/http/pprof
|
|
// f, ferr := os.Create("cpuprofile")
|
|
// if ferr != nil {
|
|
// os.Exit(1)
|
|
// }
|
|
// pprof.StartCPUProfile(f)
|
|
//
|
|
// g, ferr := os.Create("memprofile")
|
|
// if ferr != nil {
|
|
// os.Exit(1)
|
|
// }
|
|
|
|
// // * https://github.com/felixge/fgprof
|
|
// f, ferr := os.Create("cpuprofile")
|
|
// if ferr != nil {
|
|
// os.Exit(1)
|
|
// }
|
|
// fgprofStop := fgprof.Start(f, fgprof.FormatPprof)
|
|
|
|
editor = &Editor{
|
|
version: Version,
|
|
args: args,
|
|
opts: options,
|
|
startuptime: time.Now().UnixNano() / 1000,
|
|
signal: NewEditorSignal(nil),
|
|
stop: make(chan int),
|
|
notify: make(chan *Notify, 10),
|
|
cbChan: make(chan *string, 240),
|
|
chUiPrepared: make(chan bool, 1),
|
|
}
|
|
e := editor
|
|
|
|
// Prepare debug log
|
|
e.setDebuglog()
|
|
e.putLog("--- GONEOVIM STARTING ---")
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
e.ctx = ctx
|
|
|
|
var err error
|
|
// detect home dir
|
|
e.homeDir, err = homedir.Dir()
|
|
if err != nil {
|
|
e.homeDir = "~"
|
|
}
|
|
e.putLog("detecting home directory path:", e.homeDir)
|
|
|
|
// load config
|
|
e.configDir, e.config = newConfig(e.homeDir, e.opts.NoConfig)
|
|
e.putLog("Detecting the goneovim configuration directory:", e.configDir)
|
|
e.overwriteConfigByCLIOption()
|
|
|
|
// get parent process id
|
|
e.ppid = os.Getppid()
|
|
e.putLog("finished getting ppid")
|
|
|
|
// put shell environment
|
|
e.setEnvironmentVariables()
|
|
|
|
// create qapplication
|
|
e.putLog("start generating the application")
|
|
core.QCoreApplication_SetAttribute(core.Qt__AA_EnableHighDpiScaling, true)
|
|
e.app = widgets.NewQApplication(len(os.Args), os.Args)
|
|
setMyApplicationDelegate()
|
|
|
|
e.app.SetDoubleClickInterval(0)
|
|
e.putLog("finished generating the application")
|
|
|
|
var cerr, lerr error
|
|
e.initialColumns, cerr, e.initialLines, lerr = parseLinesAndColumns(args)
|
|
if cerr == nil {
|
|
editor.isSetColumns = true
|
|
}
|
|
if lerr == nil {
|
|
editor.isSetLines = true
|
|
}
|
|
|
|
// new nvim instance
|
|
signal, redrawUpdates, guiUpdates, nvimCh, uiRCh, errCh := newNvim(
|
|
e.initialColumns,
|
|
e.initialLines,
|
|
e.ctx,
|
|
)
|
|
|
|
// e.setAppDirPath(home)
|
|
|
|
e.fontCh = make(chan []*Font, 100)
|
|
go func() {
|
|
e.fontCh <- parseFont(
|
|
e.config.Editor.FontFamily,
|
|
e.config.Editor.FontSize,
|
|
e.config.Editor.FontWeight,
|
|
e.config.Editor.FontStretch,
|
|
e.config.Editor.Linespace,
|
|
e.config.Editor.Letterspace,
|
|
)
|
|
}()
|
|
|
|
e.initSVGS()
|
|
|
|
e.initColorPalette()
|
|
|
|
e.initNotifications()
|
|
|
|
e.initSysTray()
|
|
|
|
e.initSpecialKeys()
|
|
|
|
// application main window
|
|
isSetWindowState := e.initAppWindow()
|
|
e.window.Show()
|
|
|
|
// window layout
|
|
e.setWindowLayout()
|
|
|
|
// neovim workspaces
|
|
|
|
nvimErr := <-errCh
|
|
if nvimErr != nil {
|
|
fmt.Println(nvimErr)
|
|
os.Exit(1)
|
|
}
|
|
|
|
e.initWorkspaces(e.ctx, signal, redrawUpdates, guiUpdates, nvimCh, uiRCh, isSetWindowState)
|
|
|
|
e.connectAppSignals()
|
|
|
|
// go e.exitEditor(cancel, f, g)
|
|
go e.exitEditor(cancel)
|
|
|
|
e.addDockMenu()
|
|
|
|
widgets.QApplication_Exec()
|
|
}
|
|
|
|
func (e *Editor) initAppWindow() bool {
|
|
e.putLog("start preparing the application window.")
|
|
defer e.putLog("finished preparing the application window.")
|
|
|
|
e.window = frameless.CreateQFramelessWindow(frameless.FramelessConfig{
|
|
IsBorderless: e.config.Editor.BorderlessWindow,
|
|
Alpha: e.config.Editor.Transparent,
|
|
ApplyBlurEffect: e.config.Editor.EnableBackgroundBlur,
|
|
})
|
|
e.connectWindowEvents()
|
|
e.setWindowOptions()
|
|
e.setWindowSizeFromOpts()
|
|
|
|
return e.setInitialWindowState()
|
|
}
|
|
|
|
// exitEditor is to detect stop events and quit the application
|
|
// func (e *Editor) exitEditor(cancel context.CancelFunc, f, g *os.File) {
|
|
func (e *Editor) exitEditor(cancel context.CancelFunc) {
|
|
ret := <-e.stop
|
|
close(e.stop)
|
|
e.putLog("The application was quitted with the exit of Neovim.")
|
|
if runtime.GOOS == "darwin" {
|
|
e.app.DisconnectEvent()
|
|
}
|
|
e.saveAppWindowState()
|
|
cancel()
|
|
|
|
// --------------------
|
|
// profile the goneovim
|
|
// --------------------
|
|
//
|
|
// Comment out the following::
|
|
|
|
// // * built-in net/http/pprof
|
|
// pprof.StopCPUProfile()
|
|
// f.Close()
|
|
//
|
|
// runtime.GC()
|
|
// pprof.WriteHeapProfile(g)
|
|
// g.Close()
|
|
|
|
// // * https://github.com/felixge/fgprof
|
|
// fgprofStop()
|
|
// f.Close()
|
|
|
|
os.Exit(ret)
|
|
}
|
|
|
|
func (e *Editor) putLog(v ...interface{}) {
|
|
if e.opts.Debug == "" {
|
|
return
|
|
}
|
|
|
|
log.Println(
|
|
fmt.Sprintf("%07.3f", float64(time.Now().UnixNano()/1000-e.startuptime)/1000),
|
|
strings.TrimRight(strings.TrimLeft(fmt.Sprintf("%v", v), "["), "]"),
|
|
)
|
|
}
|
|
|
|
func (e *Editor) bindResizeEvent() {
|
|
e.window.ConnectResizeEvent(func(event *gui.QResizeEvent) {
|
|
if !editor.isBindNvimSizeToAppwin {
|
|
return
|
|
}
|
|
if len(e.workspaces) == 0 {
|
|
return
|
|
}
|
|
e.resizeMainWindow()
|
|
})
|
|
}
|
|
|
|
// addDockMenu add the action menu for app in the Dock.
|
|
func (e *Editor) addDockMenu() {
|
|
if runtime.GOOS != "darwin" {
|
|
return
|
|
}
|
|
appExecutable := core.QCoreApplication_ApplicationFilePath()
|
|
|
|
menu := widgets.NewQMenu(nil)
|
|
action1 := menu.AddAction("New Instance")
|
|
action1.ConnectTriggered(func(checked bool) {
|
|
go func() {
|
|
cmd := exec.Command(appExecutable)
|
|
cmd.Start()
|
|
}()
|
|
})
|
|
|
|
action2 := menu.AddAction("New Instance with -u NONE")
|
|
action2.ConnectTriggered(func(checked bool) {
|
|
go func() {
|
|
cmd := exec.Command(appExecutable, "-u", "NONE")
|
|
cmd.Start()
|
|
}()
|
|
})
|
|
|
|
for key, string := range e.config.Editor.DockmenuActions {
|
|
action := menu.AddAction(key)
|
|
strSlice := strings.Split(string, " ")
|
|
action.ConnectTriggered(func(checked bool) {
|
|
go func() {
|
|
cmd := exec.Command(appExecutable, strSlice...)
|
|
cmd.Start()
|
|
}()
|
|
})
|
|
}
|
|
|
|
menu.SetAsDockMenu()
|
|
}
|
|
|
|
func parseFont(families string, size int, weight string, stretch, linespace, letterspace int) (fonts []*Font) {
|
|
weight = strings.ToLower(weight)
|
|
var fontWeight gui.QFont__Weight
|
|
switch weight {
|
|
case "thin":
|
|
fontWeight = gui.QFont__Thin
|
|
case "extralight", "ultralight":
|
|
fontWeight = gui.QFont__ExtraLight
|
|
case "light":
|
|
fontWeight = gui.QFont__Light
|
|
case "normal", "regular":
|
|
fontWeight = gui.QFont__Normal
|
|
case "demibold", "semibold":
|
|
fontWeight = gui.QFont__DemiBold
|
|
case "bold":
|
|
fontWeight = gui.QFont__Bold
|
|
case "extrabold", "ultrabold":
|
|
fontWeight = gui.QFont__ExtraBold
|
|
case "black", "heavy":
|
|
fontWeight = gui.QFont__Black
|
|
}
|
|
|
|
for _, f := range strings.Split(families, ",") {
|
|
fonts = append(
|
|
fonts,
|
|
initFontNew(strings.TrimSpace(f), float64(size), fontWeight, stretch, linespace, letterspace),
|
|
)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// setAppDirPath
|
|
// set application working directory path
|
|
// TODO: This process is problematic and needs a better way to set up CWD
|
|
// - https://github.com/akiyosi/goneovim/issues/43
|
|
// - https://github.com/akiyosi/goneovim/issues/337
|
|
// - https://github.com/akiyosi/goneovim/issues/325
|
|
//
|
|
// Set the current working directory of the application to the HOME directory in darwin, linux.
|
|
// If this process is not executed, CWD is set to the root directory, and
|
|
// nvim plugins called as descendants of the application will not work due to lack of permission.
|
|
// e.g. #122
|
|
func (e *Editor) setAppDirPath(home string) {
|
|
if runtime.GOOS == "windows" {
|
|
return
|
|
}
|
|
if runtime.GOOS == "darwin" {
|
|
if !(e.ppid == 1 && e.macAppArg == "") {
|
|
return
|
|
}
|
|
}
|
|
if runtime.GOOS == "linux" {
|
|
if e.ppid != 1 {
|
|
return
|
|
}
|
|
}
|
|
|
|
path := core.QCoreApplication_ApplicationDirPath()
|
|
absHome, err := util.ExpandTildeToHomeDirectory(home)
|
|
if err == nil {
|
|
if path != absHome {
|
|
qdir := core.NewQDir2(path)
|
|
qdir.SetCurrent(absHome)
|
|
}
|
|
}
|
|
|
|
e.putLog("set working directory")
|
|
}
|
|
|
|
func (e *Editor) overwriteConfigByCLIOption() {
|
|
e.config.Editor.ExtTabline = e.opts.Exttabline || e.config.Editor.ExtTabline
|
|
e.config.Editor.ExtCmdline = e.opts.Extcmdline || e.config.Editor.ExtCmdline
|
|
e.config.Editor.ExtPopupmenu = e.opts.Extpopupmenu || e.config.Editor.ExtPopupmenu
|
|
e.config.Editor.ExtMessages = e.opts.Extmessages || e.config.Editor.ExtMessages
|
|
e.config.Editor.ExtCmdline = e.opts.Extmessages || e.config.Editor.ExtCmdline
|
|
|
|
e.config.Editor.StartFullscreen = e.opts.Fullscreen || e.config.Editor.StartFullscreen
|
|
e.config.Editor.StartMaximizedWindow = e.opts.Maximized || e.config.Editor.StartMaximizedWindow
|
|
}
|
|
|
|
func (e *Editor) newSplitter() {
|
|
splitter := widgets.NewQSplitter2(core.Qt__Horizontal, nil)
|
|
splitter.SetStyleSheet("* {background-color: rgba(0, 0, 0, 0);}")
|
|
splitter.SetStretchFactor(1, 100)
|
|
splitter.SetObjectName("splitter")
|
|
e.splitter = splitter
|
|
}
|
|
|
|
func (e *Editor) setDebuglog() (file *os.File) {
|
|
if e.opts.Debug == "" {
|
|
return nil
|
|
}
|
|
|
|
file, err := os.OpenFile(e.opts.Debug, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
|
|
os.Exit(1)
|
|
}
|
|
log.SetOutput(file)
|
|
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
|
|
|
|
return
|
|
}
|
|
|
|
func (e *Editor) initWorkspaces(ctx context.Context, signal *neovimSignal, redrawUpdates chan [][]interface{}, guiUpdates chan []interface{}, nvimCh chan *nvim.Nvim, uiRemoteAttachedCh chan bool, isSetWindowState bool) {
|
|
e.workspaces = []*Workspace{}
|
|
|
|
// ws := newWorkspace()
|
|
// ws.registerSignal(signal, &redrawUpdates, &guiUpdates)
|
|
// ws.initUI()
|
|
// ws.updateSize()
|
|
// go ws.bindNvim(nvimCh, uiRCh, isSetWindowState)
|
|
// e.workspaces = append(e.workspaces, ws)
|
|
// ws.widget.SetParent(e.widget)
|
|
editor.putLog("start initializing workspaces")
|
|
|
|
// Detect session file
|
|
sessionExists := false
|
|
restoreFiles := []string{}
|
|
for i := 0; i <= WORKSPACELEN; i++ {
|
|
path := filepath.Join(e.configDir, "sessions", strconv.Itoa(i)+".vim")
|
|
_, err := os.Stat(path)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
sessionExists = true
|
|
restoreFiles = append(restoreFiles, path)
|
|
|
|
}
|
|
e.doRestoreSessions = sessionExists && e.config.Workspace.RestoreSession
|
|
if len(restoreFiles) == 0 || !e.doRestoreSessions {
|
|
restoreFiles = []string{""}
|
|
}
|
|
|
|
editor.putLog("done checking sessions")
|
|
|
|
for i, file := range restoreFiles {
|
|
ws := newWorkspace()
|
|
ws.initUI()
|
|
|
|
if i == 0 {
|
|
fonts := <-e.fontCh
|
|
e.font = fonts[0]
|
|
if len(fonts) > 1 {
|
|
e.fallbackfonts = fonts[1:]
|
|
}
|
|
}
|
|
|
|
ws.initFont()
|
|
e.initAppFont()
|
|
ws.registerSignal(signal, redrawUpdates, guiUpdates)
|
|
ws.updateSize()
|
|
|
|
// Only the first nvim instance is lazy-bound to the workspace,
|
|
// but the second and subsequent instances are not lazy-bound
|
|
isLazyBind := true
|
|
if i > 0 {
|
|
isLazyBind = false
|
|
signal, redrawUpdates, guiUpdates, nvimCh, uiRemoteAttachedCh, _ = newNvim(ws.cols, ws.rows, ctx)
|
|
}
|
|
|
|
e.workspaces = append(e.workspaces, ws)
|
|
go ws.bindNvim(nvimCh, uiRemoteAttachedCh, isSetWindowState, isLazyBind, file)
|
|
}
|
|
|
|
e.putLog("done initialazing workspaces")
|
|
}
|
|
|
|
func (e *Editor) connectAppSignals() {
|
|
if e.app == nil {
|
|
return
|
|
}
|
|
|
|
if runtime.GOOS == "darwin" {
|
|
|
|
e.openingFileCh = make(chan string, 2)
|
|
go func() {
|
|
for {
|
|
openingFile := <-e.openingFileCh
|
|
|
|
file, err := os.OpenFile("/Users/akiyosi/debug3.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
|
|
os.Exit(1)
|
|
}
|
|
log.SetOutput(file)
|
|
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
|
|
log.Println(
|
|
fmt.Sprintf("%07.3f", float64(time.Now().UnixNano()/1000-e.startuptime)/1000),
|
|
strings.TrimRight(strings.TrimLeft(fmt.Sprintf("%v", openingFile), "["), "]"),
|
|
)
|
|
|
|
e.loadFileInDarwin(
|
|
openingFile,
|
|
)
|
|
}
|
|
}()
|
|
|
|
e.app.ConnectEvent(func(event *core.QEvent) bool {
|
|
switch event.Type() {
|
|
case core.QEvent__FileOpen:
|
|
// If goneovim not launched on finder (it is started in terminal)
|
|
if e.ppid != 1 {
|
|
return false
|
|
}
|
|
fileOpenEvent := gui.NewQFileOpenEventFromPointer(event.Pointer())
|
|
e.loadFileInDarwin(
|
|
fileOpenEvent.File(),
|
|
)
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
e.signal.ConnectSidebarSignal(func() {
|
|
if e.side != nil {
|
|
return
|
|
}
|
|
e.putLog("create workspace sidebar")
|
|
e.side = newWorkspaceSide()
|
|
e.side.newScrollArea()
|
|
e.side.scrollarea.Hide()
|
|
e.side.scrollarea.SetWidget(e.side.widget)
|
|
e.splitter.InsertWidget(0, e.side.scrollarea)
|
|
side := e.side
|
|
if e.config.SideBar.Visible {
|
|
side.show()
|
|
}
|
|
})
|
|
|
|
go e.toEmmitGeometrySignal()
|
|
go e.signal.ConnectGeometrySignal(func() {
|
|
e.AdjustSizeBasedOnFontmetrics(e.windowSize[0], e.windowSize[1])
|
|
})
|
|
|
|
// When an application is closed with the Close button
|
|
e.window.ConnectCloseEvent(func(event *gui.QCloseEvent) {
|
|
e.putLog("The application was closed outside of Neovim's commands, such as the Close button.")
|
|
|
|
// A request to exit the application via the close button has been issued,
|
|
// intercept this request and send quit command to the nvim process.
|
|
event.Ignore()
|
|
|
|
if e.config.Workspace.RestoreSession {
|
|
e.cleanup()
|
|
e.saveSessions()
|
|
}
|
|
|
|
var cmd string
|
|
if e.config.Editor.IgnoreSaveConfirmationWithCloseButton {
|
|
cmd = "qa!"
|
|
} else {
|
|
cmd = "confirm qa"
|
|
}
|
|
|
|
for _, ws := range e.workspaces {
|
|
go ws.nvim.Command(cmd)
|
|
}
|
|
|
|
return
|
|
})
|
|
e.putLog("done connecting UI siganal")
|
|
}
|
|
|
|
func (e *Editor) toEmmitGeometrySignal() {
|
|
for {
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
e.geometryUpdateMutex.RLock()
|
|
if e.geometryUpdateTimer == nil {
|
|
e.geometryUpdateMutex.RUnlock()
|
|
continue
|
|
}
|
|
|
|
select {
|
|
case <-e.geometryUpdateTimer.C:
|
|
e.geometryUpdateMutex.RUnlock()
|
|
e.signal.GeometrySignal()
|
|
default:
|
|
e.geometryUpdateMutex.RUnlock()
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
func (e *Editor) resizeMainWindow() {
|
|
cws := e.workspaces[e.active]
|
|
windowWidth, windowHeight, _, _ := cws.updateSize()
|
|
e.windowSize = [2]int{windowWidth, windowHeight}
|
|
|
|
if !editor.config.Editor.WindowGeometryBasedOnFontmetrics {
|
|
return
|
|
}
|
|
|
|
e.geometryUpdateMutex.Lock()
|
|
if e.geometryUpdateTimer == nil {
|
|
e.geometryUpdateTimer = time.NewTimer(200 * time.Millisecond)
|
|
} else {
|
|
if !e.geometryUpdateTimer.Stop() {
|
|
select {
|
|
case <-e.geometryUpdateTimer.C:
|
|
default:
|
|
}
|
|
}
|
|
e.geometryUpdateTimer.Reset(200 * time.Millisecond)
|
|
}
|
|
e.geometryUpdateMutex.Unlock()
|
|
}
|
|
|
|
func (e *Editor) AdjustSizeBasedOnFontmetrics(windowWidth, windowHeight int) {
|
|
if e.window.WindowState() == core.Qt__WindowFullScreen || e.isWindowMaximizing {
|
|
return
|
|
}
|
|
|
|
// quantization of window geometry with font metrics as the smallest unit of change.
|
|
geometry := editor.window.Geometry()
|
|
width := geometry.Width()
|
|
height := geometry.Height()
|
|
|
|
if !(width == windowWidth && height == windowHeight) {
|
|
e.window.Resize2(
|
|
windowWidth,
|
|
windowHeight,
|
|
)
|
|
}
|
|
}
|
|
|
|
func (e *Editor) loadFileInDarwin(file string) {
|
|
if runtime.GOOS != "darwin" {
|
|
return
|
|
}
|
|
|
|
goneovim := e.workspaces[e.active].nvim
|
|
isModified := ""
|
|
isModified, _ = goneovim.CommandOutput("echo &modified")
|
|
if isModified == "1" {
|
|
goneovim.Command(fmt.Sprintf(":tabe %s", file))
|
|
} else {
|
|
goneovim.Command(fmt.Sprintf("%s %s", e.config.Editor.FileOpenCmd, file))
|
|
}
|
|
}
|
|
|
|
func (e *Editor) initNotifications() {
|
|
e.notifications = []*Notification{}
|
|
e.notificationWidth = e.config.Editor.Width * 2 / 3
|
|
e.notifyStartPos = core.NewQPoint2(e.width-e.notificationWidth-10, e.height-30)
|
|
e.signal.ConnectNotifySignal(func() {
|
|
notify := <-e.notify
|
|
if notify.message == "" {
|
|
return
|
|
}
|
|
if notify.buttons == nil {
|
|
e.popupNotification(notify.level, notify.period, notify.message)
|
|
} else {
|
|
e.popupNotification(notify.level, notify.period, notify.message, notifyOptionArg(notify.buttons))
|
|
}
|
|
})
|
|
|
|
e.putLog("initializing notification UI")
|
|
}
|
|
|
|
func (e *Editor) initSysTray() {
|
|
if !e.config.Editor.DesktopNotifications {
|
|
return
|
|
}
|
|
pixmap := gui.NewQPixmap()
|
|
color := ""
|
|
size := 0.95
|
|
if runtime.GOOS == "darwin" {
|
|
color = "#434343"
|
|
size = 0.9
|
|
} else {
|
|
color = "#179A33"
|
|
}
|
|
svg := fmt.Sprintf(`<svg viewBox="0 0 128 128"><g transform="translate(2,3) scale(%f)"><path fill="%s" d="M72.6 80.5c.2.2.6.5.9.5h5.3c.3 0 .7-.3.9-.5l1.4-1.5c.2-.2.3-.4.3-.6l1.5-5.1c.1-.5 0-1-.3-1.3l-1.1-.9c-.2-.2-.6-.1-.9-.1h-4.8l-.2-.2-.1-.1c-.2 0-.4-.1-.6.1l-1.9 1.2c-.2 0-.3.5-.4.7l-1.6 4.9c-.2.5-.1 1.1.3 1.5l1.3 1.4zM73.4 106.9l-.4.1h-1.2l7.2-21.1c.2-.7-.1-1.5-.8-1.7l-.4-.1h-12.1c-.5.1-.9.5-1 1l-.7 2.5c-.2.7.3 1.3 1 1.5l.3-.1h1.8l-7.3 20.9c-.2.7.1 1.6.8 1.9l.4.3h11.2c.6 0 1.1-.5 1.3-1.1l.7-2.4c.3-.7-.1-1.5-.8-1.7zM126.5 87.2l-1.9-2.5v-.1c-.3-.3-.6-.6-1-.6h-7.2c-.4 0-.7.4-1 .6l-2 2.4h-3.1l-2.1-2.4v-.1c-.2-.3-.6-.5-1-.5h-4l20.2-20.2-22.6-22.4 20.2-20.8v-9l-2.8-3.6h-40.9l-3.3 3.5v2.9l-11.3-11.4-7.7 7.5-2.4-2.5h-40.4l-3.2 3.7v9.4l3 2.9h3v26.1l-14 14 14 14v32l5.2 2.9h11.6l9.1-9.5 21.6 21.6 14.5-14.5c.1.4.4.5.9.7l.4-.2h9.4c.6 0 1.1-.1 1.2-.6l.7-2c.2-.7-.1-1.3-.8-1.5l-.4.1h-.4l3.4-10.7 2.3-2.3h5l-5 15.9c-.2.7.2 1.1.9 1.4l.4-.2h9.1c.5 0 1-.1 1.2-.6l.8-1.8c.3-.7-.1-1.3-.7-1.6-.1-.1-.3 0-.5 0h-.4l4.2-13h6.1l-5.1 15.9c-.2.7.2 1.1.9 1.3l.4-.3h10c.5 0 1-.1 1.2-.6l.8-2c.3-.7-.1-1.3-.8-1.5-.1-.1-.3.1-.5.1h-.7l5.6-18.5c.2-.5.1-1.1-.1-1.4zm-63.8-82.3l11.3 11.3v4.7l3.4 4.1h1.6l-29 28v-28h3.3l2.7-4.2v-8.9l-.2-.3 6.9-6.7zm-59.8 59.2l12.1-12.1v24.2l-12.1-12.1zm38.9 38.3l58.4-60 21.4 21.5-20.2 20.2h-.1c-.3.1-.5.3-.7.5l-2.1 2.4h-2.9l-2.2-2.4c-.2-.3-.6-.6-1-.6h-8.8c-.6 0-1.1.4-1.3 1l-.8 2.5c-.2.7.1 1.3.8 1.6h1.5l-6.4 18.9-15.1 15.2-20.5-20.8z"></path></g></svg>`, size, color)
|
|
pixmap.LoadFromData2(core.NewQByteArray2(svg, len(svg)), "SVG", core.Qt__ColorOnly)
|
|
trayIcon := gui.NewQIcon2(pixmap)
|
|
image := filepath.Join(e.configDir, "trayicon.png")
|
|
if isFileExist(image) {
|
|
trayIcon = gui.NewQIcon5(image)
|
|
}
|
|
e.sysTray = widgets.NewQSystemTrayIcon2(trayIcon, e.app)
|
|
e.sysTray.Show()
|
|
e.putLog("initialize system tray")
|
|
}
|
|
|
|
func (e *Editor) setEnvironmentVariables() {
|
|
// For Linux
|
|
if runtime.GOOS == "linux" {
|
|
// // It was not a necessary process to export the following environment variables.
|
|
// exe, _ := os.Executable()
|
|
// dir, _ := filepath.Split(exe)
|
|
// _ = os.Setenv("LD_LIBRARY_PATH", dir+"lib")
|
|
// _ = os.Setenv("QT_PLUGIN_PATH", dir+"plugins")
|
|
// _ = os.Setenv("RESOURCE_NAME", "goneovim")
|
|
}
|
|
|
|
// If the OS is MacOS and the application is launched from an .app
|
|
if runtime.GOOS == "darwin" && e.ppid == 1 {
|
|
shell := os.Getenv("SHELL")
|
|
if shell == "" {
|
|
shell = os.Getenv("/bin/bash")
|
|
}
|
|
cmd := exec.Command(shell, "-l", "-c", "env", "-i")
|
|
// cmd := exec.Command("printenv")
|
|
stdout, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
e.putLog(err)
|
|
return
|
|
}
|
|
if err := cmd.Start(); err != nil {
|
|
e.putLog(err)
|
|
return
|
|
}
|
|
output, err := ioutil.ReadAll(stdout)
|
|
if err != nil {
|
|
e.putLog(err)
|
|
stdout.Close()
|
|
return
|
|
}
|
|
for _, b := range strings.Split(string(output), "\n") {
|
|
splits := strings.SplitN(b, "=", 2)
|
|
if len(splits) > 1 {
|
|
_ = os.Setenv(splits[0], splits[1])
|
|
}
|
|
}
|
|
}
|
|
|
|
// For Windows
|
|
// https://github.com/equalsraf/neovim-qt/issues/391
|
|
if runtime.GOOS == "windows" {
|
|
_ = os.Setenv("QT_AUTO_SCREEN_SCALE_FACTOR", "1")
|
|
}
|
|
|
|
e.putLog("setting environment variable")
|
|
}
|
|
|
|
func (e *Editor) initAppFont() {
|
|
e.app.SetFont(e.font.qfont, "QWidget")
|
|
e.app.SetFont(e.font.qfont, "QLabel")
|
|
|
|
e.putLog("initializing font")
|
|
}
|
|
|
|
// pushNotification is
|
|
//
|
|
// level: notify level
|
|
// period: display period
|
|
func (e *Editor) pushNotification(level NotifyLevel, p int, message string, opt ...NotifyOptionArg) {
|
|
a := NotifyOptions{}
|
|
for _, o := range opt {
|
|
o(&a)
|
|
}
|
|
n := &Notify{
|
|
level: level,
|
|
period: p,
|
|
message: message,
|
|
buttons: a.buttons,
|
|
}
|
|
e.notify <- n
|
|
e.signal.NotifySignal()
|
|
}
|
|
|
|
func (e *Editor) popupNotification(level NotifyLevel, p int, message string, opt ...NotifyOptionArg) {
|
|
e.updateNotificationPos()
|
|
notification := newNotification(level, p, message, opt...)
|
|
notification.widget.SetParent(e.window)
|
|
notification.widget.AdjustSize()
|
|
x := e.notifyStartPos.X()
|
|
y := e.notifyStartPos.Y() - notification.widget.Height() - 4
|
|
notification.widget.Move2(x, y)
|
|
e.notifyStartPos = core.NewQPoint2(x, y)
|
|
e.notifications = append(e.notifications, notification)
|
|
notification.show()
|
|
}
|
|
|
|
func (e *Editor) initColorPalette() {
|
|
rgbAccent := hexToRGBA(e.config.SideBar.AccentColor)
|
|
fg := newRGBA(180, 185, 190, 1)
|
|
bg := newRGBA(9, 13, 17, 1)
|
|
c := &ColorPalette{
|
|
e: e,
|
|
bg: bg,
|
|
fg: fg,
|
|
selectedBg: bg.brend(rgbAccent, 0.3),
|
|
matchFg: rgbAccent,
|
|
}
|
|
|
|
e.colors = c
|
|
e.colors.update()
|
|
|
|
e.putLog("initializing color palette")
|
|
}
|
|
|
|
func parseLinesAndColumns(args []string) (int, error, int, error) {
|
|
var columns, lines int = -1, -1
|
|
|
|
pattern := regexp.MustCompile(`lines=(\d+)|columns=(\d+)|vim\.o\["(lines|columns)"\]=(\d+)`)
|
|
|
|
for _, arg := range args {
|
|
matches := pattern.FindAllStringSubmatch(arg, -1)
|
|
for _, match := range matches {
|
|
if match[1] != "" { // "lines=XX"
|
|
if val, err := strconv.Atoi(match[1]); err == nil {
|
|
lines = val
|
|
}
|
|
} else if match[2] != "" { // "columns=XX"
|
|
if val, err := strconv.Atoi(match[2]); err == nil {
|
|
columns = val
|
|
}
|
|
} else if match[3] == "lines" && match[4] != "" { // vim.o["lines"]=XX
|
|
if val, err := strconv.Atoi(match[4]); err == nil {
|
|
lines = val
|
|
}
|
|
} else if match[3] == "columns" && match[4] != "" { // vim.o["columns"]=XX
|
|
if val, err := strconv.Atoi(match[4]); err == nil {
|
|
columns = val
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if columns == -1 && lines == -1 {
|
|
return 100, fmt.Errorf("columns are not set"), 50, fmt.Errorf("lines are not set")
|
|
}
|
|
|
|
var cerr error
|
|
if columns == -1 {
|
|
columns = 100
|
|
cerr = fmt.Errorf("columns are not set")
|
|
}
|
|
var lerr error
|
|
if lines == -1 {
|
|
lines = 50
|
|
lerr = fmt.Errorf("lines are not set")
|
|
}
|
|
|
|
return columns, cerr, lines, lerr
|
|
}
|
|
|
|
func (c *ColorPalette) update() {
|
|
fg := c.fg
|
|
bg := c.bg
|
|
c.selectedBg = bg.brend(hexToRGBA(c.e.config.SideBar.AccentColor), 0.3)
|
|
c.inactiveFg = warpColor(bg, -80)
|
|
c.comment = warpColor(fg, -80)
|
|
c.abyss = warpColor(bg, 5)
|
|
c.sideBarFg = warpColor(fg, -5)
|
|
c.sideBarBg = warpColor(bg, -5)
|
|
c.sideBarSelectedItemBg = warpColor(bg, -15)
|
|
c.scrollBarFg = warpColor(bg, -20)
|
|
c.scrollBarBg = bg
|
|
c.widgetFg = warpColor(fg, 5)
|
|
c.widgetBg = warpColor(bg, -10)
|
|
c.widgetInputArea = warpColor(bg, -30)
|
|
c.minimapCurrentRegion = warpColor(bg, 20)
|
|
if c.e.config.Editor.WindowSeparatorColor != "" {
|
|
c.windowSeparator = hexToRGBA(c.e.config.Editor.WindowSeparatorColor)
|
|
} else if c.e.config.Editor.WindowSeparatorTheme == "light" && c.e.config.Editor.WindowSeparatorColor == "" {
|
|
if fg.R < 250 && fg.G < 250 && fg.B < 250 {
|
|
c.windowSeparator = warpColor(fg, 10)
|
|
} else {
|
|
c.windowSeparator = warpColor(fg, -10)
|
|
}
|
|
} else if c.e.config.Editor.WindowSeparatorTheme == "dark" && c.e.config.Editor.WindowSeparatorColor == "" {
|
|
if bg.R > 10 && bg.G > 10 && bg.B > 10 {
|
|
c.windowSeparator = warpColor(bg, 10)
|
|
} else {
|
|
c.windowSeparator = warpColor(bg, -10)
|
|
}
|
|
}
|
|
c.indentGuide = warpColor(bg, -30)
|
|
}
|
|
|
|
func (e *Editor) updateGUIColor() {
|
|
e.putLog("start updating GUI color")
|
|
e.workspaces[e.active].updateWorkspaceColor()
|
|
|
|
// Do not use frameless drawing on linux
|
|
if runtime.GOOS == "linux" {
|
|
e.window.TitleBar.Hide()
|
|
e.window.WindowWidget.SetStyleSheet(fmt.Sprintf(" #QFramelessWidget { background-color: rgba(%d, %d, %d, %f); border-radius: 0px;}", e.colors.bg.R, e.colors.bg.G, e.colors.bg.B, e.config.Editor.Transparent))
|
|
e.window.SetWindowFlag(core.Qt__FramelessWindowHint, false)
|
|
e.window.SetWindowFlag(core.Qt__NoDropShadowWindowHint, false)
|
|
e.window.Show()
|
|
} else {
|
|
e.window.SetupWidgetColor((uint16)(e.colors.bg.R), (uint16)(e.colors.bg.G), (uint16)(e.colors.bg.B))
|
|
e.window.SetupTitleColor((uint16)(e.colors.fg.R), (uint16)(e.colors.fg.G), (uint16)(e.colors.fg.B))
|
|
}
|
|
|
|
// e.window.SetWindowOpacity(1.0)
|
|
e.putLog("finished updating GUI color")
|
|
}
|
|
|
|
func hexToRGBA(hex string) *RGBA {
|
|
format := "#%02x%02x%02x"
|
|
if len(hex) == 4 {
|
|
format = "#%1x%1x%1x"
|
|
}
|
|
var r, g, b uint8
|
|
n, err := fmt.Sscanf(hex, format, &r, &g, &b)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
if n != 3 {
|
|
return nil
|
|
}
|
|
rgba := &RGBA{
|
|
R: (int)(r),
|
|
G: (int)(g),
|
|
B: (int)(b),
|
|
A: 1,
|
|
}
|
|
|
|
return rgba
|
|
}
|
|
|
|
func (e *Editor) setWindowSizeFromOpts() {
|
|
e.window.SetupBorderSize(e.config.Editor.Margin)
|
|
e.window.SetupWindowGap(e.config.Editor.Gap)
|
|
|
|
if e.opts.Geometry != "" {
|
|
width, height := e.setWindowSize(e.opts.Geometry)
|
|
e.config.Editor.Width = width
|
|
e.config.Editor.Height = height
|
|
}
|
|
}
|
|
|
|
func (e *Editor) setWindowSize(s string) (int, int) {
|
|
var width, height int
|
|
var err error
|
|
width, err = strconv.Atoi(strings.SplitN(s, "x", 2)[0])
|
|
if err != nil || width < 40 {
|
|
width = 40
|
|
}
|
|
height, err = strconv.Atoi(strings.SplitN(s, "x", 2)[1])
|
|
if err != nil || height < 30 {
|
|
height = 30
|
|
}
|
|
|
|
return width, height
|
|
}
|
|
|
|
func (e *Editor) restoreWindow() {
|
|
if !e.config.Editor.RestoreWindowGeometry {
|
|
return
|
|
}
|
|
|
|
settings := core.NewQSettings("neovim", "goneovim", nil)
|
|
geometry := settings.Value("geometry", core.NewQVariant13(core.NewQByteArray()))
|
|
state := settings.Value("windowState", core.NewQVariant13(core.NewQByteArray()))
|
|
|
|
geometryBA := geometry.ToByteArray()
|
|
stateBA := state.ToByteArray()
|
|
|
|
if geometryBA.Length() != 0 {
|
|
e.window.RestoreGeometry(geometryBA)
|
|
// isRestoreGeometry = true
|
|
}
|
|
if stateBA.Length() != 0 {
|
|
e.window.RestoreFramelessState(stateBA, 0)
|
|
// isRestoreState = true
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (e *Editor) connectWindowEvents() {
|
|
e.window.ConnectKeyPressEvent(e.keyPress)
|
|
e.window.ConnectKeyReleaseEvent(e.keyRelease)
|
|
e.bindResizeEvent()
|
|
e.window.ConnectShowEvent(func(event *gui.QShowEvent) {
|
|
editor.putLog("show application window")
|
|
})
|
|
|
|
e.window.InstallEventFilter(e.window)
|
|
e.window.ConnectEventFilter(func(watched *core.QObject, event *core.QEvent) bool {
|
|
switch event.Type() {
|
|
case core.QEvent__ActivationChange:
|
|
if e.window.IsActiveWindow() {
|
|
e.isWindowNowActivated = true
|
|
e.isWindowNowInactivated = false
|
|
} else if !e.window.IsActiveWindow() {
|
|
e.isWindowNowActivated = false
|
|
e.isWindowNowInactivated = true
|
|
}
|
|
case core.QEvent__WindowStateChange:
|
|
if e.window.WindowState() == core.Qt__WindowMaximized {
|
|
if !e.isWindowMaximizing {
|
|
e.isWindowMaximizing = true
|
|
|
|
if editor.config.Editor.WindowGeometryBasedOnFontmetrics {
|
|
go e.window.WindowMaximize()
|
|
}
|
|
}
|
|
} else {
|
|
e.isWindowMaximizing = false
|
|
}
|
|
default:
|
|
}
|
|
|
|
return e.window.QFramelessDefaultEventFilter(watched, event)
|
|
})
|
|
}
|
|
|
|
func (e *Editor) setWindowOptions() {
|
|
// e.window.SetupTitle("Neovim")
|
|
e.window.SetMinimumSize2(40, 30)
|
|
e.window.SetAttribute(core.Qt__WA_KeyCompression, false)
|
|
e.window.SetAcceptDrops(true)
|
|
}
|
|
|
|
func (e *Editor) setInitialWindowState() (isSetWindowState bool) {
|
|
e.width = e.config.Editor.Width
|
|
e.height = e.config.Editor.Height
|
|
if e.config.Editor.HideTitlebar {
|
|
e.window.IsTitlebarHidden = true
|
|
e.window.TitleBar.Hide()
|
|
}
|
|
|
|
// isRestoreGeometry, isRestoreState := e.restoreWindow()
|
|
|
|
// If command line options are given, they take priority.
|
|
if e.config.Editor.StartFullscreen ||
|
|
e.config.Editor.StartMaximizedWindow {
|
|
if e.config.Editor.StartFullscreen {
|
|
e.window.WindowFullScreen()
|
|
} else if e.config.Editor.StartMaximizedWindow {
|
|
e.window.WindowMaximize()
|
|
}
|
|
isSetWindowState = true
|
|
} else {
|
|
if e.config.Editor.RestoreWindowGeometry && e.opts.Geometry == "" {
|
|
e.restoreWindow()
|
|
} else {
|
|
e.window.Resize2(e.width, e.height)
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (e *Editor) setWindowLayout() {
|
|
// window layout
|
|
l := widgets.NewQBoxLayout(widgets.QBoxLayout__RightToLeft, nil)
|
|
l.SetContentsMargins(0, 0, 0, 0)
|
|
l.SetSpacing(0)
|
|
e.window.SetupContent(l)
|
|
|
|
// window content
|
|
e.widget = widgets.NewQWidget(nil, 0)
|
|
e.newSplitter()
|
|
e.splitter.InsertWidget(1, e.widget)
|
|
l.AddWidget(e.splitter, 1, 0)
|
|
|
|
e.putLog("window content layout done")
|
|
}
|
|
|
|
func isFileExist(filename string) bool {
|
|
_, err := os.Stat(filename)
|
|
return err == nil
|
|
}
|
|
|
|
func (e *Editor) workspaceAdd() {
|
|
if len(e.workspaces) == WORKSPACELEN {
|
|
return
|
|
}
|
|
editor.isSetGuiColor = false
|
|
|
|
ws := newWorkspace()
|
|
ws.initUI()
|
|
ws.initFont()
|
|
|
|
ws.updateSize()
|
|
signal, redrawUpdates, guiUpdates, nvimCh, uiRemoteAttachedCh, _ := newNvim(ws.cols, ws.rows, e.ctx)
|
|
ws.registerSignal(signal, redrawUpdates, guiUpdates)
|
|
|
|
e.workspaces = append(e.workspaces, ws)
|
|
e.active = len(e.workspaces) - 1
|
|
|
|
e.workspaces[e.active] = ws
|
|
ws.bindNvim(nvimCh, uiRemoteAttachedCh, false, false, "")
|
|
e.workspaceUpdate()
|
|
}
|
|
|
|
func (e *Editor) workspaceSwitch(index int) {
|
|
index--
|
|
if index < 0 || index >= len(e.workspaces) {
|
|
return
|
|
}
|
|
e.active = index
|
|
e.workspaceUpdate()
|
|
}
|
|
|
|
func (e *Editor) workspaceNext() {
|
|
e.active++
|
|
if e.active >= len(e.workspaces) {
|
|
e.active = 0
|
|
}
|
|
e.workspaceUpdate()
|
|
}
|
|
|
|
func (e *Editor) workspacePrevious() {
|
|
e.active--
|
|
if e.active < 0 {
|
|
e.active = len(e.workspaces) - 1
|
|
}
|
|
e.workspaceUpdate()
|
|
}
|
|
|
|
func (e *Editor) workspaceUpdate() {
|
|
if e.side == nil {
|
|
return
|
|
}
|
|
for i, ws := range e.workspaces {
|
|
if i == e.active {
|
|
ws.hide()
|
|
ws.show()
|
|
} else {
|
|
ws.hide()
|
|
}
|
|
}
|
|
for i := 0; i < len(e.side.items) && i < len(e.workspaces); i++ {
|
|
if e.side.items[i] == nil {
|
|
continue
|
|
}
|
|
if e.side.items[i].label.Text() == "" {
|
|
e.workspaces[i].setCwd(e.workspaces[i].cwdlabel)
|
|
}
|
|
e.side.items[i].setSideItemLabel(i)
|
|
// e.side.items[i].setText(e.workspaces[i].cwdlabel)
|
|
e.side.items[i].show()
|
|
}
|
|
for i := len(e.workspaces); i < len(e.side.items); i++ {
|
|
if e.side.items[i] == nil {
|
|
continue
|
|
}
|
|
e.side.items[i].hide()
|
|
}
|
|
}
|
|
|
|
func (e *Editor) close(exitcode int) {
|
|
e.stopOnce.Do(func() {
|
|
e.stop <- exitcode
|
|
})
|
|
}
|
|
|
|
func (e *Editor) saveAppWindowState() {
|
|
e.putLog("save application window state")
|
|
settings := core.NewQSettings("neovim", "goneovim", nil)
|
|
settings.SetValue("geometry", core.NewQVariant13(e.window.SaveGeometry()))
|
|
settings.SetValue("windowState", core.NewQVariant13(e.window.SaveState(0)))
|
|
}
|
|
|
|
func (e *Editor) cleanup() {
|
|
// TODO We need to kill the minimap nvim process explicitly?
|
|
|
|
if !e.config.Workspace.RestoreSession {
|
|
return
|
|
}
|
|
sessions := filepath.Join(e.configDir, "sessions")
|
|
os.RemoveAll(sessions)
|
|
os.MkdirAll(sessions, 0755)
|
|
}
|
|
|
|
func (e *Editor) saveSessions() {
|
|
if !e.config.Workspace.RestoreSession {
|
|
return
|
|
}
|
|
|
|
sessions := filepath.Join(e.configDir, "sessions")
|
|
|
|
for i, ws := range e.workspaces {
|
|
if ws.uiRemoteAttached {
|
|
continue
|
|
}
|
|
sessionPath := filepath.Join(sessions, strconv.Itoa(i)+".vim")
|
|
ws.nvim.Command("mksession " + sessionPath)
|
|
}
|
|
}
|