2017-11-09 02:11:05 +00:00
package editor
2017-03-14 01:52:44 +00:00
import (
2022-06-13 21:08:53 +09:00
"context"
2017-03-14 01:52:44 +00:00
"fmt"
2025-06-22 17:57:34 +09:00
"io"
2020-12-23 00:07:42 +09:00
"log"
2018-01-08 10:23:16 +00:00
"os"
2019-12-08 23:24:57 +09:00
"os/exec"
2018-01-08 10:23:16 +00:00
"path/filepath"
2024-06-02 14:50:33 +09:00
"regexp"
2017-05-12 04:07:54 +01:00
"runtime"
2018-01-08 10:23:16 +00:00
"strconv"
2017-03-14 07:20:31 +00:00
"strings"
2017-06-02 10:41:28 +01:00
"sync"
2020-12-21 19:57:18 +09:00
"time"
2017-03-14 01:52:44 +00:00
2020-06-06 22:33:04 +09:00
"github.com/akiyosi/goneovim/util"
2019-03-13 11:50:52 +09:00
frameless "github.com/akiyosi/goqtframelesswindow"
2023-07-16 00:53:36 +09:00
"github.com/akiyosi/qt/core"
"github.com/akiyosi/qt/gui"
"github.com/akiyosi/qt/widgets"
2025-06-10 20:39:23 +09:00
// "github.com/felixge/fgprof"
2019-11-19 00:32:34 +09:00
homedir "github.com/mitchellh/go-homedir"
2023-01-26 01:33:47 +09:00
"github.com/neovim/go-client/nvim"
2017-03-14 01:52:44 +00:00
)
var editor * Editor
2019-10-22 22:50:56 +09:00
const (
2023-11-16 22:43:10 +09:00
WORKSPACELEN = 10
NVIMCALLTIMEOUT = 320
2023-12-13 22:26:33 +09:00
NVIMCALLTIMEOUT2 = 45
2019-10-22 22:50:56 +09:00
)
2020-12-12 01:39:52 +09:00
type editorSignal struct {
core . QObject
_ func ( ) ` signal:"notifySignal" `
_ func ( ) ` signal:"sidebarSignal" `
2022-09-12 23:54:59 +09:00
_ func ( ) ` signal:"geometrySignal" `
2020-12-12 01:39:52 +09:00
}
2018-12-27 21:03:21 +09:00
// ColorPalette is
2018-12-27 12:38:42 +09:00
type ColorPalette struct {
2019-06-12 22:14:48 +09:00
e * Editor
2019-06-12 21:58:35 +09:00
2018-12-31 16:43:04 +09:00
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
2019-05-31 16:41:37 +09:00
windowSeparator * RGBA
2019-06-07 02:00:14 +09:00
indentGuide * RGBA
2018-12-27 12:38:42 +09:00
}
2019-06-30 19:35:25 +09:00
// NotifyButton is
type NotifyButton struct {
action func ( )
text string
}
2018-12-21 12:18:41 +09:00
// Notify is
2018-08-16 19:31:36 +09:00
type Notify struct {
message string
buttons [ ] * NotifyButton
2021-12-10 21:24:04 +09:00
level NotifyLevel
period int
2018-08-16 19:31:36 +09:00
}
2020-12-23 00:07:42 +09:00
type Options struct {
2023-05-06 03:53:47 +02:00
Geometry string ` long:"geometry" description:"Initial window geometry [e.g. --geometry=800x600]" `
2022-04-19 11:49:35 +09:00
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]" `
2023-05-06 03:53:47 +02:00
Nvim string ` long:"nvim" description:"Executable nvim path to attach [e.g. --nvim=/path/to/nvim]" `
2022-04-19 11:49:35 +09:00
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:"" `
2022-04-24 15:46:56 +09:00
Nofork bool ` long:"nofork" description:"Run in foreground" `
2023-07-01 14:37:58 +09:00
NoConfig bool ` long:"noconfig" description:"Run Goneovim with no config. (Equivalent to loading an empty settings.toml)" `
2019-11-13 22:49:25 +09:00
}
2017-03-14 01:52:44 +00:00
// Editor is the editor
type Editor struct {
2023-05-03 01:39:21 +09:00
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
cbChan chan * string
chUiPrepared chan bool
2024-06-05 23:55:03 +09:00
openingFileCh chan string
2022-09-12 23:54:59 +09:00
geometryUpdateTimer * time . Timer
2023-05-03 01:39:21 +09:00
sysTray * widgets . QSystemTrayIcon
side * WorkspaceSide
savedGeometry * core . QByteArray
prefixToMapMetaKey string
configDir string
homeDir string
version string
config gonvimConfig
opts Options
font * Font
2023-11-02 22:41:48 +09:00
fallbackfonts [ ] * Font
2025-03-07 16:59:28 +09:00
fontCh chan [ ] * Font
fontErrors [ ] string
2023-05-03 01:39:21 +09:00
notifications [ ] * Notification
workspaces [ ] * Workspace
args [ ] string
keyControl core . Qt__Key
keyCmd core . Qt__Key
2022-09-12 23:54:59 +09:00
windowSize [ 2 ] int
2023-05-03 01:39:21 +09:00
width int
active int
startuptime int64
iconSize int
height int
notificationWidth int
stopOnce sync . Once
muMetaKey sync . Mutex
2024-01-11 12:36:44 +09:00
geometryUpdateMutex sync . RWMutex
2023-05-03 01:39:21 +09:00
doRestoreSessions bool
2024-03-07 22:24:42 +09:00
initialColumns int
initialLines int
isSetColumns bool
isSetLines bool
2023-05-03 01:39:21 +09:00
isSetGuiColor bool
isDisplayNotifications bool
isKeyAutoRepeating bool
isWindowResized bool
isWindowNowActivated bool
isWindowNowInactivated bool
isExtWinNowActivated bool
isExtWinNowInactivated bool
isHideMouse bool
isBindNvimSizeToAppwin bool
isUiPrepared bool
2022-09-12 23:54:59 +09:00
isWindowMaximizing bool
2017-06-16 08:43:05 +01: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 ( )
}
2018-05-12 22:48:15 +09:00
highlight . bold = hl . bold
highlight . italic = hl . italic
2017-03-14 01:52:44 +00:00
return highlight
}
2017-06-06 02:42:11 +01:00
2017-11-09 02:11:05 +00:00
// InitEditor is
2021-08-06 23:13:30 +09:00
func InitEditor ( options Options , args [ ] string ) {
2020-12-23 00:07:42 +09:00
2022-12-28 23:22:33 +09:00
// --------------------
2023-01-26 01:33:47 +09:00
// For profiling goneovim
2022-12-28 23:22:33 +09:00
// --------------------
//
// https://blog.golang.org/pprof
// After running the app, exec the following:
// $ go tool pprof -http=localhost:9090 cpuprofile
//
// Comment out the following::
2023-08-16 23:54:23 +09:00
// // * built-in net/http/pprof
2022-12-28 23:22:33 +09:00
// f, ferr := os.Create("cpuprofile")
// if ferr != nil {
// os.Exit(1)
// }
// pprof.StartCPUProfile(f)
2023-08-16 23:54:23 +09:00
//
// g, ferr := os.Create("memprofile")
// if ferr != nil {
// os.Exit(1)
// }
2022-12-28 23:22:33 +09:00
// // * https://github.com/felixge/fgprof
// f, ferr := os.Create("cpuprofile")
// if ferr != nil {
// os.Exit(1)
// }
// fgprofStop := fgprof.Start(f, fgprof.FormatPprof)
2020-12-23 00:07:42 +09:00
editor = & Editor {
2023-01-26 01:33:47 +09:00
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 ) ,
2020-12-21 19:57:18 +09:00
}
2020-12-23 00:07:42 +09:00
e := editor
2020-12-21 19:57:18 +09:00
2023-01-26 01:33:47 +09:00
// Prepare debug log
e . setDebuglog ( )
2020-12-23 00:07:42 +09:00
e . putLog ( "--- GONEOVIM STARTING ---" )
2020-12-04 23:14:06 +09:00
2022-06-13 21:08:53 +09:00
ctx , cancel := context . WithCancel ( context . Background ( ) )
2023-01-26 01:33:47 +09:00
e . ctx = ctx
2022-06-13 21:08:53 +09:00
2023-01-26 01:33:47 +09:00
var err error
2020-08-01 14:22:34 +09:00
// detect home dir
2023-01-26 01:33:47 +09:00
e . homeDir , err = homedir . Dir ( )
2018-08-19 23:29:26 +09:00
if err != nil {
2023-01-26 01:33:47 +09:00
e . homeDir = "~"
2018-08-19 23:29:26 +09:00
}
2023-03-26 00:21:01 +09:00
e . putLog ( "detecting home directory path:" , e . homeDir )
2020-12-21 19:57:18 +09:00
2023-01-26 01:33:47 +09:00
// load config
2023-07-01 14:37:58 +09:00
e . configDir , e . config = newConfig ( e . homeDir , e . opts . NoConfig )
2023-03-26 00:21:01 +09:00
e . putLog ( "Detecting the goneovim configuration directory:" , e . configDir )
2023-01-26 01:33:47 +09:00
e . overwriteConfigByCLIOption ( )
2020-12-04 23:14:06 +09:00
2023-01-26 01:33:47 +09:00
// put shell environment
e . setEnvironmentVariables ( )
2020-12-21 19:57:18 +09:00
2025-07-09 00:53:21 +09:00
// set application working directory path
e . setAppDirPath ( e . homeDir )
2023-01-26 01:33:47 +09:00
// create qapplication
2020-12-23 00:07:42 +09:00
e . putLog ( "start generating the application" )
core . QCoreApplication_SetAttribute ( core . Qt__AA_EnableHighDpiScaling , true )
2019-11-24 12:52:15 +09:00
e . app = widgets . NewQApplication ( len ( os . Args ) , os . Args )
2023-11-18 14:32:37 +09:00
setMyApplicationDelegate ( )
2023-03-18 00:34:23 +09:00
e . app . SetDoubleClickInterval ( 0 )
2020-12-23 00:07:42 +09:00
e . putLog ( "finished generating the application" )
2020-12-21 19:57:18 +09:00
2025-03-07 16:59:28 +09:00
e . initNotifications ( )
2024-03-07 22:24:42 +09:00
var cerr , lerr error
e . initialColumns , cerr , e . initialLines , lerr = parseLinesAndColumns ( args )
if cerr == nil {
editor . isSetColumns = true
}
if lerr == nil {
editor . isSetLines = true
}
2023-01-26 01:33:47 +09:00
// new nvim instance
2024-03-07 22:24:42 +09:00
signal , redrawUpdates , guiUpdates , nvimCh , uiRCh , errCh := newNvim (
e . initialColumns ,
e . initialLines ,
e . ctx ,
)
2020-12-27 21:32:52 +09:00
2022-04-23 01:31:11 +09:00
// e.setAppDirPath(home)
2020-06-06 22:33:04 +09:00
2023-11-02 22:41:48 +09:00
e . fontCh = make ( chan [ ] * Font , 100 )
2020-12-05 14:36:12 +09:00
go func ( ) {
2023-11-02 22:41:48 +09:00
e . fontCh <- parseFont (
2024-01-12 13:29:26 +09:00
e . config . Editor . FontFamily ,
e . config . Editor . FontSize ,
2023-11-02 22:41:48 +09:00
e . config . Editor . FontWeight ,
e . config . Editor . FontStretch ,
e . config . Editor . Linespace ,
2021-11-30 23:15:52 +09:00
e . config . Editor . Letterspace ,
2020-12-05 14:36:12 +09:00
)
} ( )
2020-12-21 19:57:18 +09:00
2020-12-11 21:59:47 +09:00
e . initSVGS ( )
2020-12-23 00:07:42 +09:00
2020-12-11 21:59:47 +09:00
e . initColorPalette ( )
2020-12-23 00:07:42 +09:00
e . initSysTray ( )
2020-12-21 19:57:18 +09:00
2023-01-26 01:33:47 +09:00
e . initSpecialKeys ( )
2020-08-01 14:22:34 +09:00
// application main window
2023-01-26 01:33:47 +09:00
isSetWindowState := e . initAppWindow ( )
2023-05-03 01:39:21 +09:00
e . window . Show ( )
2020-12-21 19:57:18 +09:00
2020-08-01 14:22:34 +09:00
// window layout
2023-01-26 01:33:47 +09:00
e . setWindowLayout ( )
2020-12-21 19:57:18 +09:00
2020-08-01 14:22:34 +09:00
// neovim workspaces
2023-04-28 23:13:31 +09:00
nvimErr := <- errCh
if nvimErr != nil {
fmt . Println ( nvimErr )
os . Exit ( 1 )
}
2023-01-26 01:33:47 +09:00
e . initWorkspaces ( e . ctx , signal , redrawUpdates , guiUpdates , nvimCh , uiRCh , isSetWindowState )
2019-10-24 17:04:33 +09:00
2020-10-05 23:02:44 +09:00
e . connectAppSignals ( )
2023-08-16 23:54:23 +09:00
// go e.exitEditor(cancel, f, g)
2025-06-10 20:39:23 +09:00
// go e.exitEditor(cancel, f, fgprofStop)
2023-01-26 01:33:47 +09:00
go e . exitEditor ( cancel )
2020-12-12 01:39:52 +09:00
2023-01-26 01:33:47 +09:00
e . addDockMenu ( )
2022-11-06 22:50:22 +09:00
2023-01-26 01:33:47 +09:00
widgets . QApplication_Exec ( )
}
2022-11-06 22:50:22 +09:00
2023-01-26 01:33:47 +09:00
func ( e * Editor ) initAppWindow ( ) bool {
e . putLog ( "start preparing the application window." )
defer e . putLog ( "finished preparing the application window." )
2022-11-06 22:50:22 +09:00
2024-02-07 15:59:17 +09:00
e . window = frameless . CreateQFramelessWindow ( frameless . FramelessConfig {
IsBorderless : e . config . Editor . BorderlessWindow ,
Alpha : e . config . Editor . Transparent ,
ApplyBlurEffect : e . config . Editor . EnableBackgroundBlur ,
} )
2023-01-26 01:33:47 +09:00
e . connectWindowEvents ( )
e . setWindowOptions ( )
e . setWindowSizeFromOpts ( )
2019-06-12 21:58:35 +09:00
2023-01-26 01:33:47 +09:00
return e . setInitialWindowState ( )
}
2019-06-12 21:58:35 +09:00
2023-01-26 01:33:47 +09:00
// exitEditor is to detect stop events and quit the application
2023-08-16 23:54:23 +09:00
// func (e *Editor) exitEditor(cancel context.CancelFunc, f, g *os.File) {
2025-06-10 20:39:23 +09:00
// func (e *Editor) exitEditor(cancel context.CancelFunc, f *os.File, fgprofStop func() error) {
2023-01-26 01:33:47 +09:00
func ( e * Editor ) exitEditor ( cancel context . CancelFunc ) {
ret := <- e . stop
close ( e . stop )
e . putLog ( "The application was quitted with the exit of Neovim." )
2020-11-28 01:28:56 +09:00
if runtime . GOOS == "darwin" {
2023-01-26 01:33:47 +09:00
e . app . DisconnectEvent ( )
2020-11-28 01:28:56 +09:00
}
2023-01-26 01:33:47 +09:00
e . saveAppWindowState ( )
cancel ( )
2020-11-28 01:28:56 +09:00
2023-01-26 01:33:47 +09:00
// --------------------
// profile the goneovim
// --------------------
//
// Comment out the following::
2023-08-16 23:54:23 +09:00
2023-01-26 01:33:47 +09:00
// // * built-in net/http/pprof
// pprof.StopCPUProfile()
// f.Close()
2023-08-16 23:54:23 +09:00
//
// runtime.GC()
// pprof.WriteHeapProfile(g)
// g.Close()
2020-07-12 01:20:22 +09:00
2023-01-26 01:33:47 +09:00
// // * https://github.com/felixge/fgprof
// fgprofStop()
// f.Close()
os . Exit ( ret )
2019-06-12 21:58:35 +09:00
}
2020-12-23 00:07:42 +09:00
func ( e * Editor ) putLog ( v ... interface { } ) {
2021-01-25 23:24:53 +09:00
if e . opts . Debug == "" {
2020-12-23 00:07:42 +09:00
return
2020-12-04 23:14:06 +09:00
}
2020-12-23 00:07:42 +09:00
log . Println (
fmt . Sprintf ( "%07.3f" , float64 ( time . Now ( ) . UnixNano ( ) / 1000 - e . startuptime ) / 1000 ) ,
2022-01-13 17:57:38 +09:00
strings . TrimRight ( strings . TrimLeft ( fmt . Sprintf ( "%v" , v ) , "[" ) , "]" ) ,
2020-12-23 00:07:42 +09:00
)
2020-12-04 23:14:06 +09:00
}
2023-01-26 01:33:47 +09:00
func ( e * Editor ) bindResizeEvent ( ) {
e . window . ConnectResizeEvent ( func ( event * gui . QResizeEvent ) {
2023-05-03 01:39:21 +09:00
if ! editor . isBindNvimSizeToAppwin {
return
}
if len ( e . workspaces ) == 0 {
return
}
2023-01-26 01:33:47 +09:00
e . resizeMainWindow ( )
} )
}
2021-08-27 23:17:25 +09:00
// 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 ( )
}
2023-11-02 22:41:48 +09:00
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 , "," ) {
2025-03-07 16:59:28 +09:00
font := initFontNew ( strings . TrimSpace ( f ) , float64 ( size ) , fontWeight , stretch , linespace , letterspace )
fonts = append ( fonts , font )
ok := checkValidFont ( f )
if ! ok {
editor . fontErrors = append ( editor . fontErrors , f )
continue
}
2023-11-02 22:41:48 +09:00
}
return
}
2025-03-07 16:59:28 +09:00
func ( e * Editor ) showFontErrors ( ) {
if len ( e . fontErrors ) == 0 {
return
}
for _ , fontError := range e . fontErrors {
go e . pushNotification (
NotifyWarn ,
2025-03-10 12:03:56 +09:00
6 ,
2025-03-07 16:59:28 +09:00
fmt . Sprintf ( "The specified font family '%s' was not found on this system." , fontError ) ,
notifyOptionArg ( [ ] * NotifyButton { } ) ,
)
}
}
2020-08-01 14:22:34 +09:00
// setAppDirPath
2023-01-26 01:33:47 +09:00
// set application working directory path
// TODO: This process is problematic and needs a better way to set up CWD
2024-06-02 14:50:33 +09:00
// - https://github.com/akiyosi/goneovim/issues/43
// - https://github.com/akiyosi/goneovim/issues/337
// - https://github.com/akiyosi/goneovim/issues/325
//
2020-10-05 23:02:44 +09:00
// Set the current working directory of the application to the HOME directory in darwin, linux.
2020-08-01 14:22:34 +09:00
// 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
2020-10-05 23:02:44 +09:00
func ( e * Editor ) setAppDirPath ( home string ) {
if runtime . GOOS == "windows" {
return
}
2025-07-09 00:53:21 +09:00
if os . Getenv ( "TERM" ) != "" {
return
2020-10-05 23:02:44 +09:00
}
2020-08-01 14:22:34 +09:00
path := core . QCoreApplication_ApplicationDirPath ( )
absHome , err := util . ExpandTildeToHomeDirectory ( home )
if err == nil {
if path != absHome {
qdir := core . NewQDir2 ( path )
qdir . SetCurrent ( absHome )
}
}
2023-01-26 01:33:47 +09:00
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
2020-08-01 14:22:34 +09:00
}
2019-10-20 18:13:56 +09:00
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" )
2020-08-28 00:54:52 +09:00
e . splitter = splitter
2019-10-20 18:13:56 +09:00
}
2023-01-26 01:33:47 +09:00
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 )
2022-05-19 01:26:04 +09:00
2023-01-26 01:33:47 +09:00
os . Exit ( 1 )
2022-05-19 01:26:04 +09:00
}
2023-01-26 01:33:47 +09:00
log . SetOutput ( file )
log . SetFlags ( log . LstdFlags | log . Lmicroseconds )
return
}
2023-04-21 00:08:05 +09:00
func ( e * Editor ) initWorkspaces ( ctx context . Context , signal * neovimSignal , redrawUpdates chan [ ] [ ] interface { } , guiUpdates chan [ ] interface { } , nvimCh chan * nvim . Nvim , uiRemoteAttachedCh chan bool , isSetWindowState bool ) {
2023-01-26 01:33:47 +09:00
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)
2023-05-03 01:39:21 +09:00
editor . putLog ( "start initializing workspaces" )
2023-01-26 01:33:47 +09:00
// 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
2018-10-11 21:37:36 +09:00
}
2023-01-26 01:33:47 +09:00
sessionExists = true
restoreFiles = append ( restoreFiles , path )
2018-10-11 21:37:36 +09:00
}
2023-01-26 01:33:47 +09:00
e . doRestoreSessions = sessionExists && e . config . Workspace . RestoreSession
if len ( restoreFiles ) == 0 || ! e . doRestoreSessions {
restoreFiles = [ ] string { "" }
}
2023-05-03 01:39:21 +09:00
editor . putLog ( "done checking sessions" )
2023-01-26 01:33:47 +09:00
for i , file := range restoreFiles {
ws := newWorkspace ( )
ws . initUI ( )
2023-05-03 01:39:21 +09:00
if i == 0 {
2023-11-02 22:41:48 +09:00
fonts := <- e . fontCh
e . font = fonts [ 0 ]
if len ( fonts ) > 1 {
e . fallbackfonts = fonts [ 1 : ]
}
2023-05-03 01:39:21 +09:00
}
ws . initFont ( )
2023-07-14 09:39:44 +09:00
e . initAppFont ( )
2023-05-03 01:39:21 +09:00
ws . registerSignal ( signal , redrawUpdates , guiUpdates )
2023-01-26 01:33:47 +09:00
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
2023-04-28 23:13:31 +09:00
signal , redrawUpdates , guiUpdates , nvimCh , uiRemoteAttachedCh , _ = newNvim ( ws . cols , ws . rows , ctx )
2018-10-11 21:37:36 +09:00
}
2023-01-26 01:33:47 +09:00
2018-10-11 21:37:36 +09:00
e . workspaces = append ( e . workspaces , ws )
2023-05-03 01:39:21 +09:00
go ws . bindNvim ( nvimCh , uiRemoteAttachedCh , isSetWindowState , isLazyBind , file )
2018-10-11 21:37:36 +09:00
}
2023-01-26 01:33:47 +09:00
e . putLog ( "done initialazing workspaces" )
2019-06-12 21:58:35 +09:00
}
2018-10-11 21:37:36 +09:00
2020-10-05 23:02:44 +09:00
func ( e * Editor ) connectAppSignals ( ) {
if e . app == nil {
return
}
2024-06-05 23:55:03 +09:00
2023-01-26 01:33:47 +09:00
if runtime . GOOS == "darwin" {
2024-06-05 23:55:03 +09:00
2024-09-01 21:16:22 +09:00
if e . openingFileCh == nil {
e . openingFileCh = make ( chan string , 2 )
}
2024-06-05 23:55:03 +09:00
go func ( ) {
for {
openingFile := <- e . openingFileCh
2025-03-02 00:36:31 +09:00
if strings . Join ( editor . args , "" ) != "" {
continue
}
2024-06-05 23:55:03 +09:00
e . loadFileInDarwin (
openingFile ,
)
}
} ( )
2023-01-26 01:33:47 +09:00
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)
2025-07-09 00:53:21 +09:00
if os . Getenv ( "TERM" ) != "" {
2023-01-26 01:33:47 +09:00
return false
}
fileOpenEvent := gui . NewQFileOpenEventFromPointer ( event . Pointer ( ) )
2024-06-05 23:55:03 +09:00
e . loadFileInDarwin (
fileOpenEvent . File ( ) ,
)
2019-06-12 21:58:35 +09:00
}
2023-01-26 01:33:47 +09:00
return true
} )
}
e . signal . ConnectSidebarSignal ( func ( ) {
if e . side != nil {
return
2018-05-25 01:00:23 +09:00
}
2023-01-26 01:33:47 +09:00
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 ( )
}
} )
2022-09-12 23:54:59 +09:00
go e . toEmmitGeometrySignal ( )
go e . signal . ConnectGeometrySignal ( func ( ) {
e . AdjustSizeBasedOnFontmetrics ( e . windowSize [ 0 ] , e . windowSize [ 1 ] )
} )
2023-01-26 01:33:47 +09:00
// 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
2019-06-12 21:58:35 +09:00
} )
2023-01-26 01:33:47 +09:00
e . putLog ( "done connecting UI siganal" )
2020-10-05 23:02:44 +09:00
}
2022-09-12 23:54:59 +09:00
func ( e * Editor ) toEmmitGeometrySignal ( ) {
for {
time . Sleep ( 100 * time . Millisecond )
2024-01-11 12:36:44 +09:00
e . geometryUpdateMutex . RLock ( )
2022-09-12 23:54:59 +09:00
if e . geometryUpdateTimer == nil {
2024-01-11 12:36:44 +09:00
e . geometryUpdateMutex . RUnlock ( )
2022-09-12 23:54:59 +09:00
continue
}
2024-01-11 12:36:44 +09:00
2022-09-12 23:54:59 +09:00
select {
case <- e . geometryUpdateTimer . C :
2024-01-11 12:36:44 +09:00
e . geometryUpdateMutex . RUnlock ( )
2022-09-12 23:54:59 +09:00
e . signal . GeometrySignal ( )
default :
2024-01-11 12:36:44 +09:00
e . geometryUpdateMutex . RUnlock ( )
2022-09-12 23:54:59 +09:00
continue
}
}
}
2022-03-04 17:28:52 +09:00
func ( e * Editor ) resizeMainWindow ( ) {
2022-04-09 22:13:43 +09:00
cws := e . workspaces [ e . active ]
2023-01-26 01:33:47 +09:00
windowWidth , windowHeight , _ , _ := cws . updateSize ( )
2022-09-12 23:54:59 +09:00
e . windowSize = [ 2 ] int { windowWidth , windowHeight }
2025-03-10 12:03:56 +09:00
e . relocateNotifications ( )
2022-03-04 17:28:52 +09:00
2022-04-09 22:13:43 +09:00
if ! editor . config . Editor . WindowGeometryBasedOnFontmetrics {
return
}
2022-03-04 17:28:52 +09:00
2024-01-11 12:36:44 +09:00
e . geometryUpdateMutex . Lock ( )
2022-09-12 23:54:59 +09:00
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 )
}
2024-01-11 12:36:44 +09:00
e . geometryUpdateMutex . Unlock ( )
2022-09-12 23:54:59 +09:00
}
func ( e * Editor ) AdjustSizeBasedOnFontmetrics ( windowWidth , windowHeight int ) {
if e . window . WindowState ( ) == core . Qt__WindowFullScreen || e . isWindowMaximizing {
2022-07-10 13:55:25 +09:00
return
}
2022-04-09 22:13:43 +09:00
// quantization of window geometry with font metrics as the smallest unit of change.
geometry := editor . window . Geometry ( )
width := geometry . Width ( )
height := geometry . Height ( )
2022-03-04 17:28:52 +09:00
2022-04-09 22:13:43 +09:00
if ! ( width == windowWidth && height == windowHeight ) {
2022-03-04 17:28:52 +09:00
e . window . Resize2 (
windowWidth ,
windowHeight ,
)
}
}
2024-06-05 23:55:03 +09:00
func ( e * Editor ) loadFileInDarwin ( file string ) {
2020-10-05 23:02:44 +09:00
if runtime . GOOS != "darwin" {
return
}
goneovim := e . workspaces [ e . active ] . nvim
isModified := ""
isModified , _ = goneovim . CommandOutput ( "echo &modified" )
if isModified == "1" {
2024-06-05 23:55:03 +09:00
goneovim . Command ( fmt . Sprintf ( ":tabe %s" , file ) )
2020-10-05 23:02:44 +09:00
} else {
2024-06-05 23:55:03 +09:00
goneovim . Command ( fmt . Sprintf ( "%s %s" , e . config . Editor . FileOpenCmd , file ) )
2020-10-05 23:02:44 +09:00
}
2018-01-05 08:07:29 +00:00
}
2018-12-29 17:31:44 +09:00
func ( e * Editor ) initNotifications ( ) {
e . notifications = [ ] * Notification { }
2019-06-12 21:58:35 +09:00
e . notificationWidth = e . config . Editor . Width * 2 / 3
2018-12-29 17:31:44 +09:00
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 ) )
}
} )
2023-01-26 01:33:47 +09:00
e . putLog ( "initializing notification UI" )
2018-12-29 17:31:44 +09:00
}
2019-07-11 12:18:17 +09:00
func ( e * Editor ) initSysTray ( ) {
2019-07-20 17:34:13 +09:00
if ! e . config . Editor . DesktopNotifications {
return
}
2019-07-11 18:28:37 +09:00
pixmap := gui . NewQPixmap ( )
2019-07-12 00:28:27 +09:00
color := ""
size := 0.95
if runtime . GOOS == "darwin" {
2024-11-04 11:07:45 +09:00
if isDarkMode ( ) {
color = "#ffffff"
} else {
color = "#434343"
}
2019-07-12 00:28:27 +09:00
size = 0.9
} else {
2024-11-04 11:07:45 +09:00
if isDarkMode ( ) {
color = "#ffffff"
} else {
color = "#179A33"
}
2019-07-12 00:28:27 +09:00
}
2024-11-04 11:07:45 +09:00
2019-07-12 00:28:27 +09:00
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 )
2019-07-11 18:28:37 +09:00
pixmap . LoadFromData2 ( core . NewQByteArray2 ( svg , len ( svg ) ) , "SVG" , core . Qt__ColorOnly )
trayIcon := gui . NewQIcon2 ( pixmap )
2020-10-01 00:11:46 +09:00
image := filepath . Join ( e . configDir , "trayicon.png" )
2019-07-15 20:41:02 +09:00
if isFileExist ( image ) {
trayIcon = gui . NewQIcon5 ( image )
}
2019-07-11 12:18:17 +09:00
e . sysTray = widgets . NewQSystemTrayIcon2 ( trayIcon , e . app )
2019-07-11 15:47:36 +09:00
e . sysTray . Show ( )
2020-12-23 00:07:42 +09:00
e . putLog ( "initialize system tray" )
2019-07-11 12:18:17 +09:00
}
2024-11-04 11:07:45 +09:00
func isDarkMode ( ) bool {
plt := gui . NewQPalette ( )
txtColor := plt . Color2 ( gui . QPalette__WindowText )
winColor := plt . Color2 ( gui . QPalette__Window )
return txtColor . LightnessF ( ) > winColor . LightnessF ( )
}
2023-01-26 01:33:47 +09:00
func ( e * Editor ) setEnvironmentVariables ( ) {
2021-05-03 09:58:59 +09:00
// For Linux
2019-06-12 21:58:35 +09:00
if runtime . GOOS == "linux" {
2021-05-03 09:58:59 +09:00
// // 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")
2019-06-12 21:58:35 +09:00
}
2021-05-03 09:58:59 +09:00
// If the OS is MacOS and the application is launched from an .app
2025-06-22 17:57:34 +09:00
if runtime . GOOS == "darwin" && os . Getenv ( "TERM" ) == "" {
2019-12-07 13:19:58 +09:00
shell := os . Getenv ( "SHELL" )
if shell == "" {
2025-06-22 17:57:34 +09:00
shell = "/bin/zsh" // fallback
2019-12-07 13:19:58 +09:00
}
2025-06-22 17:57:34 +09:00
cmd := exec . Command ( shell , "-l" , "-c" , "env" )
2019-12-07 13:19:58 +09:00
stdout , err := cmd . StdoutPipe ( )
if err != nil {
2021-02-01 22:30:09 +09:00
e . putLog ( err )
2019-12-07 13:19:58 +09:00
return
}
2025-06-22 17:57:34 +09:00
2019-12-08 23:24:57 +09:00
if err := cmd . Start ( ) ; err != nil {
2021-02-01 22:30:09 +09:00
e . putLog ( err )
2019-12-07 13:19:58 +09:00
return
}
2025-06-22 17:57:34 +09:00
output , err := io . ReadAll ( stdout )
stdout . Close ( )
2019-12-07 13:19:58 +09:00
if err != nil {
2021-02-01 22:30:09 +09:00
e . putLog ( err )
2019-12-07 13:19:58 +09:00
return
}
2025-06-22 17:57:34 +09:00
for _ , line := range strings . Split ( string ( output ) , "\n" ) {
splits := strings . SplitN ( line , "=" , 2 )
if len ( splits ) == 2 {
2019-12-07 13:19:58 +09:00
_ = os . Setenv ( splits [ 0 ] , splits [ 1 ] )
}
}
}
2021-05-03 09:58:59 +09:00
// For Windows
// https://github.com/equalsraf/neovim-qt/issues/391
if runtime . GOOS == "windows" {
_ = os . Setenv ( "QT_AUTO_SCREEN_SCALE_FACTOR" , "1" )
}
2023-01-26 01:33:47 +09:00
e . putLog ( "setting environment variable" )
2019-06-12 21:58:35 +09:00
}
2023-01-26 01:33:47 +09:00
func ( e * Editor ) initAppFont ( ) {
2024-01-12 13:29:26 +09:00
e . app . SetFont ( e . font . qfont , "QWidget" )
e . app . SetFont ( e . font . qfont , "QLabel" )
2023-01-26 01:33:47 +09:00
e . putLog ( "initializing font" )
2019-06-12 21:58:35 +09:00
}
2022-05-27 20:55:49 +09:00
// pushNotification is
2024-06-02 14:50:33 +09:00
//
// level: notify level
// period: display period
2018-08-19 23:29:26 +09:00
func ( e * Editor ) pushNotification ( level NotifyLevel , p int , message string , opt ... NotifyOptionArg ) {
2020-12-23 00:07:42 +09:00
a := NotifyOptions { }
2018-08-16 19:31:36 +09:00
for _ , o := range opt {
2020-12-23 00:07:42 +09:00
o ( & a )
2018-08-16 19:31:36 +09:00
}
n := & Notify {
level : level ,
2018-08-19 23:29:26 +09:00
period : p ,
2018-08-16 19:31:36 +09:00
message : message ,
2020-12-23 00:07:42 +09:00
buttons : a . buttons ,
2018-08-16 19:31:36 +09:00
}
e . notify <- n
e . signal . NotifySignal ( )
}
2018-08-19 23:29:26 +09:00
func ( e * Editor ) popupNotification ( level NotifyLevel , p int , message string , opt ... NotifyOptionArg ) {
2020-08-10 23:19:00 +09:00
e . updateNotificationPos ( )
2018-08-19 23:29:26 +09:00
notification := newNotification ( level , p , message , opt ... )
2018-08-15 17:44:53 +09:00
notification . widget . SetParent ( e . window )
notification . widget . AdjustSize ( )
x := e . notifyStartPos . X ( )
2018-08-18 16:13:44 +09:00
y := e . notifyStartPos . Y ( ) - notification . widget . Height ( ) - 4
2018-08-15 17:44:53 +09:00
notification . widget . Move2 ( x , y )
e . notifyStartPos = core . NewQPoint2 ( x , y )
2018-08-15 21:45:15 +09:00
e . notifications = append ( e . notifications , notification )
2018-08-15 17:44:53 +09:00
notification . show ( )
}
2019-06-12 21:58:35 +09:00
func ( e * Editor ) initColorPalette ( ) {
rgbAccent := hexToRGBA ( e . config . SideBar . AccentColor )
2018-12-28 01:40:33 +09:00
fg := newRGBA ( 180 , 185 , 190 , 1 )
bg := newRGBA ( 9 , 13 , 17 , 1 )
2019-06-12 21:58:35 +09:00
c := & ColorPalette {
e : e ,
2018-12-31 16:43:04 +09:00
bg : bg ,
fg : fg ,
2018-12-28 15:46:52 +09:00
selectedBg : bg . brend ( rgbAccent , 0.3 ) ,
2018-12-31 16:43:04 +09:00
matchFg : rgbAccent ,
2018-12-27 12:38:42 +09:00
}
2019-06-12 21:58:35 +09:00
e . colors = c
e . colors . update ( )
2023-01-26 01:33:47 +09:00
e . putLog ( "initializing color palette" )
2018-12-27 12:38:42 +09:00
}
2024-03-07 22:24:42 +09:00
func parseLinesAndColumns ( args [ ] string ) ( int , error , int , error ) {
2024-06-02 14:50:33 +09:00
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" )
}
2024-03-07 22:24:42 +09:00
var cerr error
2024-06-02 14:50:33 +09:00
if columns == - 1 {
columns = 100
2024-03-07 22:24:42 +09:00
cerr = fmt . Errorf ( "columns are not set" )
2024-06-02 14:50:33 +09:00
}
2024-03-07 22:24:42 +09:00
var lerr error
2024-06-02 14:50:33 +09:00
if lines == - 1 {
lines = 50
2024-03-07 22:24:42 +09:00
lerr = fmt . Errorf ( "lines are not set" )
2024-06-02 14:50:33 +09:00
}
2024-03-07 22:24:42 +09:00
2024-06-02 14:50:33 +09:00
return columns , cerr , lines , lerr
2024-03-07 22:24:42 +09:00
}
2018-12-27 21:03:21 +09:00
func ( c * ColorPalette ) update ( ) {
fg := c . fg
bg := c . bg
2020-08-16 09:00:22 +09:00
c . selectedBg = bg . brend ( hexToRGBA ( c . e . config . SideBar . AccentColor ) , 0.3 )
2019-03-20 22:41:06 +09:00
c . inactiveFg = warpColor ( bg , - 80 )
c . comment = warpColor ( fg , - 80 )
2018-12-28 01:40:33 +09:00
c . abyss = warpColor ( bg , 5 )
2019-03-20 22:41:06 +09:00
c . sideBarFg = warpColor ( fg , - 5 )
c . sideBarBg = warpColor ( bg , - 5 )
c . sideBarSelectedItemBg = warpColor ( bg , - 15 )
c . scrollBarFg = warpColor ( bg , - 20 )
2018-12-27 21:03:21 +09:00
c . scrollBarBg = bg
2019-03-20 22:41:06 +09:00
c . widgetFg = warpColor ( fg , 5 )
c . widgetBg = warpColor ( bg , - 10 )
c . widgetInputArea = warpColor ( bg , - 30 )
c . minimapCurrentRegion = warpColor ( bg , 20 )
2020-08-16 09:00:22 +09:00
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 )
}
}
2019-10-29 21:53:26 +09:00
c . indentGuide = warpColor ( bg , - 30 )
2018-12-27 21:03:21 +09:00
}
func ( e * Editor ) updateGUIColor ( ) {
2023-01-26 01:33:47 +09:00
e . putLog ( "start updating GUI color" )
2019-01-01 18:34:51 +09:00
e . workspaces [ e . active ] . updateWorkspaceColor ( )
2019-03-02 21:40:40 +09:00
2019-10-27 15:59:27 +09:00
// Do not use frameless drawing on linux
if runtime . GOOS == "linux" {
2020-12-12 01:39:52 +09:00
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 ( )
2019-10-27 15:59:27 +09:00
} else {
2020-12-12 01:39:52 +09:00
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 ) )
2019-05-09 23:22:43 +09:00
}
2020-08-10 23:19:00 +09:00
// e.window.SetWindowOpacity(1.0)
2023-01-26 01:33:47 +09:00
e . putLog ( "finished updating GUI color" )
2018-12-27 21:03:21 +09:00
}
2018-06-24 13:36:41 +09:00
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
}
2020-03-14 20:30:30 +09:00
func ( e * Editor ) setWindowSizeFromOpts ( ) {
2023-01-26 01:33:47 +09:00
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
2019-11-19 00:02:56 +09:00
}
2020-03-14 20:30:30 +09:00
}
2019-11-19 00:02:56 +09:00
2020-03-14 20:30:30 +09:00
func ( e * Editor ) setWindowSize ( s string ) ( int , int ) {
2019-11-19 00:02:56 +09:00
var width , height int
2020-03-14 20:30:30 +09:00
var err error
2025-06-21 14:41:19 +02:00
parsed_s := strings . SplitN ( s , "x" , 2 )
if len ( parsed_s ) != 2 {
// TODO: Error message to user?
return 40 , 30
}
width , err = strconv . Atoi ( parsed_s [ 0 ] )
2022-04-24 21:14:00 +09:00
if err != nil || width < 40 {
width = 40
2019-11-19 00:02:56 +09:00
}
2025-06-21 14:41:19 +02:00
height , err = strconv . Atoi ( parsed_s [ 1 ] )
2022-04-24 21:14:00 +09:00
if err != nil || height < 30 {
height = 30
2020-03-14 20:30:30 +09:00
}
return width , height
2019-11-19 00:02:56 +09:00
}
2023-01-26 01:33:47 +09:00
func ( e * Editor ) restoreWindow ( ) {
2022-02-11 11:53:54 +09:00
if ! e . config . Editor . RestoreWindowGeometry {
return
}
settings := core . NewQSettings ( "neovim" , "goneovim" , nil )
2025-08-07 22:12:28 +09:00
geometry := settings . Value ( "geometry" , core . NewQVariant13 ( core . NewQByteArray ( ) ) )
2022-02-11 11:53:54 +09:00
geometryBA := geometry . ToByteArray ( )
if geometryBA . Length ( ) != 0 {
e . window . RestoreGeometry ( geometryBA )
}
2025-08-07 22:12:28 +09:00
// Restoring `windowState` causes problems when the previous session did
// something like “maximize → manual resize.” The maximize flag survives,
// so on the next launch the window opens maximized and the original
// geometry is lost. Therefore, we restore only the geometry and skip
// `windowState`.
// state := settings.Value("windowState", core.NewQVariant13(core.NewQByteArray()))
// stateBA := state.ToByteArray()
// if stateBA.Length() != 0 {
// e.window.RestoreFramelessState(stateBA, 0)
// // isRestoreState = true
// }
2022-02-11 11:53:54 +09:00
return
}
2022-05-08 17:04:24 +09:00
func ( e * Editor ) connectWindowEvents ( ) {
e . window . ConnectKeyPressEvent ( e . keyPress )
e . window . ConnectKeyReleaseEvent ( e . keyRelease )
2023-05-03 01:39:21 +09:00
e . bindResizeEvent ( )
2023-01-26 01:33:47 +09:00
e . window . ConnectShowEvent ( func ( event * gui . QShowEvent ) {
editor . putLog ( "show application window" )
} )
2022-05-08 17:04:24 +09:00
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
}
2022-09-12 23:54:59 +09:00
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
}
2022-05-08 17:04:24 +09:00
default :
}
return e . window . QFramelessDefaultEventFilter ( watched , event )
} )
}
2018-12-29 15:40:57 +09:00
func ( e * Editor ) setWindowOptions ( ) {
2023-05-03 01:39:21 +09:00
// e.window.SetupTitle("Neovim")
2022-04-12 18:09:46 +09:00
e . window . SetMinimumSize2 ( 40 , 30 )
2020-03-22 22:51:13 +09:00
e . window . SetAttribute ( core . Qt__WA_KeyCompression , false )
2018-12-29 15:40:57 +09:00
e . window . SetAcceptDrops ( true )
}
2023-01-26 01:33:47 +09:00
func ( e * Editor ) setInitialWindowState ( ) ( isSetWindowState bool ) {
2020-08-12 00:02:45 +09:00
e . width = e . config . Editor . Width
e . height = e . config . Editor . Height
2022-07-10 13:55:25 +09:00
if e . config . Editor . HideTitlebar {
e . window . IsTitlebarHidden = true
e . window . TitleBar . Hide ( )
}
2022-02-11 11:53:54 +09:00
2023-01-26 01:33:47 +09:00
// isRestoreGeometry, isRestoreState := e.restoreWindow()
2022-02-11 11:53:54 +09:00
2022-03-11 21:16:50 +09:00
// If command line options are given, they take priority.
2023-01-26 01:33:47 +09:00
if e . config . Editor . StartFullscreen ||
e . config . Editor . StartMaximizedWindow {
if e . config . Editor . StartFullscreen {
2022-02-11 11:53:54 +09:00
e . window . WindowFullScreen ( )
2023-01-26 01:33:47 +09:00
} else if e . config . Editor . StartMaximizedWindow {
2022-02-11 11:53:54 +09:00
e . window . WindowMaximize ( )
}
2023-01-26 01:33:47 +09:00
isSetWindowState = true
2022-03-11 21:16:50 +09:00
} else {
2023-01-26 01:33:47 +09:00
if e . config . Editor . RestoreWindowGeometry && e . opts . Geometry == "" {
e . restoreWindow ( )
} else {
2022-03-11 21:16:50 +09:00
e . window . Resize2 ( e . width , e . height )
}
2022-02-11 11:53:54 +09:00
}
2023-01-26 01:33:47 +09:00
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" )
2020-08-12 00:02:45 +09:00
}
2018-04-19 21:07:48 +09:00
func isFileExist ( filename string ) bool {
2018-04-30 16:58:26 +09:00
_ , err := os . Stat ( filename )
return err == nil
2018-04-19 21:07:48 +09:00
}
2023-01-26 01:33:47 +09:00
func ( e * Editor ) workspaceAdd ( ) {
2021-05-13 23:52:40 +09:00
if len ( e . workspaces ) == WORKSPACELEN {
2021-02-14 16:27:43 +09:00
return
}
2018-12-28 13:53:55 +09:00
editor . isSetGuiColor = false
2023-01-26 01:33:47 +09:00
ws := newWorkspace ( )
ws . initUI ( )
2024-01-12 13:29:26 +09:00
ws . initFont ( )
2023-01-26 01:33:47 +09:00
ws . updateSize ( )
2023-04-28 23:13:31 +09:00
signal , redrawUpdates , guiUpdates , nvimCh , uiRemoteAttachedCh , _ := newNvim ( ws . cols , ws . rows , e . ctx )
2023-01-26 01:33:47 +09:00
ws . registerSignal ( signal , redrawUpdates , guiUpdates )
2018-05-20 12:06:29 +09:00
2023-05-03 01:39:21 +09:00
e . workspaces = append ( e . workspaces , ws )
2018-05-01 19:50:47 +09:00
e . active = len ( e . workspaces ) - 1
2018-01-05 09:13:50 +00:00
e . workspaces [ e . active ] = ws
2023-05-03 01:39:21 +09:00
ws . bindNvim ( nvimCh , uiRemoteAttachedCh , false , false , "" )
e . workspaceUpdate ( )
2018-01-08 10:23:16 +00:00
}
func ( e * Editor ) workspaceSwitch ( index int ) {
index --
if index < 0 || index >= len ( e . workspaces ) {
return
}
e . active = index
2018-01-05 09:13:50 +00:00
e . workspaceUpdate ( )
}
func ( e * Editor ) workspaceNext ( ) {
e . active ++
if e . active >= len ( e . workspaces ) {
e . active = 0
}
e . workspaceUpdate ( )
}
2018-01-11 06:11:34 +00:00
func ( e * Editor ) workspacePrevious ( ) {
e . active --
if e . active < 0 {
e . active = len ( e . workspaces ) - 1
}
e . workspaceUpdate ( )
}
2018-01-05 09:13:50 +00:00
func ( e * Editor ) workspaceUpdate ( ) {
2021-01-22 22:55:14 +09:00
if e . side == nil {
2018-12-28 22:28:21 +09:00
return
}
2018-01-05 09:13:50 +00:00
for i , ws := range e . workspaces {
if i == e . active {
2021-05-05 10:31:34 +09:00
ws . hide ( )
2018-01-05 09:13:50 +00:00
ws . show ( )
} else {
ws . hide ( )
}
}
2021-01-22 22:55:14 +09:00
for i := 0 ; i < len ( e . side . items ) && i < len ( e . workspaces ) ; i ++ {
2021-05-08 01:58:26 +09:00
if e . side . items [ i ] == nil {
continue
}
2021-05-08 08:42:19 +09:00
if e . side . items [ i ] . label . Text ( ) == "" {
e . workspaces [ i ] . setCwd ( e . workspaces [ i ] . cwdlabel )
}
2021-01-22 22:55:14 +09:00
e . side . items [ i ] . setSideItemLabel ( i )
2021-05-05 10:31:34 +09:00
// e.side.items[i].setText(e.workspaces[i].cwdlabel)
2021-01-22 22:55:14 +09:00
e . side . items [ i ] . show ( )
2018-01-08 10:23:16 +00:00
}
2021-01-22 22:55:14 +09:00
for i := len ( e . workspaces ) ; i < len ( e . side . items ) ; i ++ {
2021-05-08 01:58:26 +09:00
if e . side . items [ i ] == nil {
continue
}
2021-01-22 22:55:14 +09:00
e . side . items [ i ] . hide ( )
2018-01-08 10:23:16 +00:00
}
2018-01-05 09:13:50 +00:00
}
2022-03-01 14:30:47 +09:00
func ( e * Editor ) close ( exitcode int ) {
2018-01-08 10:23:16 +00:00
e . stopOnce . Do ( func ( ) {
2022-03-01 14:30:47 +09:00
e . stop <- exitcode
2018-01-08 10:23:16 +00:00
} )
}
2022-02-11 11:53:54 +09:00
func ( e * Editor ) saveAppWindowState ( ) {
2023-01-26 01:33:47 +09:00
e . putLog ( "save application window state" )
2022-02-11 11:53:54 +09:00
settings := core . NewQSettings ( "neovim" , "goneovim" , nil )
settings . SetValue ( "geometry" , core . NewQVariant13 ( e . window . SaveGeometry ( ) ) )
settings . SetValue ( "windowState" , core . NewQVariant13 ( e . window . SaveState ( 0 ) ) )
}
2018-01-08 10:23:16 +00:00
func ( e * Editor ) cleanup ( ) {
2022-06-13 21:08:53 +09:00
// TODO We need to kill the minimap nvim process explicitly?
2022-05-19 01:26:04 +09:00
if ! e . config . Workspace . RestoreSession {
return
}
2020-10-01 00:11:46 +09:00
sessions := filepath . Join ( e . configDir , "sessions" )
2018-01-08 10:23:16 +00:00
os . RemoveAll ( sessions )
os . MkdirAll ( sessions , 0755 )
2022-05-19 01:26:04 +09:00
}
2018-01-11 06:11:34 +00:00
2022-05-19 01:26:04 +09:00
func ( e * Editor ) saveSessions ( ) {
if ! e . config . Workspace . RestoreSession {
return
}
2018-01-11 06:11:34 +00:00
2022-05-19 01:26:04 +09:00
sessions := filepath . Join ( e . configDir , "sessions" )
2018-01-08 10:23:16 +00:00
for i , ws := range e . workspaces {
2023-01-26 01:33:47 +09:00
if ws . uiRemoteAttached {
continue
}
2018-01-08 10:23:16 +00:00
sessionPath := filepath . Join ( sessions , strconv . Itoa ( i ) + ".vim" )
2023-01-26 01:33:47 +09:00
ws . nvim . Command ( "mksession " + sessionPath )
2018-01-08 10:23:16 +00:00
}
}