func main() { flag.Parse() log.AddFilter("file", log.FINEST, log.NewFileLogWriter("debug.log", *rotateLog)) // Replace Global Logger filter so that it does not interfere with the ui log.AddFilter("stdout", log.DEBUG, log.NewFileLogWriter("debug.log", *rotateLog)) defer func() { py.NewLock() py.Finalize() }() if err := termbox.Init(); err != nil { log.Error(err) log.Close() return } defer func() { termbox.Close() log.Debug(util.Prof) if err := recover(); err != nil { log.Critical(err) panic(err) } }() t := createFrontend() t.editor.Init() go t.renderthread() t.loop() }
func (t *tbfe) renderthread() { pc := 0 dorender := func() { defer func() { if r := recover(); r != nil { log.Error("Panic in renderthread: %v\n%s", r, string(debug.Stack())) if pc > 1 { panic(r) } pc++ } }() termbox.Clear(defaultFg, defaultBg) t.lock.Lock() vs := make([]*backend.View, 0, len(t.layout)) ls := make([]layout, 0, len(t.layout)) for v, l := range t.layout { vs = append(vs, v) ls = append(ls, l) } t.lock.Unlock() for i, v := range vs { t.renderView(v, ls[i]) } termbox.Flush() } for range t.dorender { dorender() } }
func setColorMode() { var ( mode256 bool pal = make([]termbox.RGB, 0, 256) ) if err := termbox.SetColorMode(termbox.ColorMode256); err != nil { log.Error("Unable to use 256 color mode: %s", err) } else { log.Debug("Using 256 color mode") mode256 = true } if !mode256 { pal = pal[:10] // Not correct, but whatever pal[termbox.ColorBlack] = termbox.RGB{R: 0, G: 0, B: 0} pal[termbox.ColorWhite] = termbox.RGB{R: 255, G: 255, B: 255} pal[termbox.ColorRed] = termbox.RGB{R: 255, G: 0, B: 0} pal[termbox.ColorGreen] = termbox.RGB{R: 0, G: 255, B: 0} pal[termbox.ColorBlue] = termbox.RGB{R: 0, G: 0, B: 255} pal[termbox.ColorMagenta] = termbox.RGB{R: 255, G: 0, B: 255} pal[termbox.ColorYellow] = termbox.RGB{R: 255, G: 255, B: 0} pal[termbox.ColorCyan] = termbox.RGB{R: 0, G: 255, B: 255} diff := func(i, j byte) int { v := int(i) - int(j) if v < 0 { return -v } return v } palLut = func(col render.Colour) termbox.Attribute { mindist := 10000000 mini := 0 for i, c := range pal { if dist := diff(c.R, col.R) + diff(c.G, col.G) + diff(c.B, col.B); dist < mindist { mindist = dist mini = i } } return termbox.Attribute(mini) } } else { palLut = func(col render.Colour) termbox.Attribute { tc := termbox.RGB{R: col.R, G: col.G, B: col.B} for i, c := range pal { if c == tc { return termbox.Attribute(i) } } l := len(pal) log.Debug("Adding colour: %d %+v %+v", l, col, tc) pal = append(pal, tc) termbox.SetColorPalette(pal) return termbox.Attribute(l) } } }
// called when a view has loaded func (f *frontend) onLoad(bv *backend.View) { w := f.windows[bv.Window()] v := w.views[bv] if v == nil { log.Error("Couldn't find loaded view") return } v.Title = bv.FileName() w.qw.Call("setTabTitle", v.id, v.Title) }
// called when a view is closed func (f *frontend) onClose(bv *backend.View) { w := f.windows[bv.Window()] v := w.views[bv] if v == nil { log.Error("Couldn't find closed view...") return } w.qw.Call("removeTab", v.id) delete(w.views, bv) }
func setSchemeSettings(ed *backend.Editor) { s := ed.GetColorScheme(ed.Settings().Get("color_scheme", "").(string)) if s == nil { log.Error("No colour scheme to set defaults from") return } defaultFg = palLut(s.Global().Foreground) defaultBg = palLut(s.Global().Background) }
func (f *frontend) onStatusChanged(bv *backend.View) { w := f.windows[bv.Window()] v := w.views[bv] if v == nil { log.Error("Couldn't find status changed view") return } if v.qv == nil { return } v.qv.Call("onStatusChanged") }
func (f *frontend) onSelectionModified(bv *backend.View) { w := f.windows[bv.Window()] v := w.views[bv] if v == nil { log.Error("Couldn't find modified view") return } if v.qv == nil { return } v.qv.Call("onSelectionModified") }
func (f *frontend) loop() (err error) { ed := backend.GetEditor() // TODO: As InitCallback doc says initiation code to be deferred until // after the UI is up and running. but because we dont have any // scheme we are initing editor before the UI comes up. ed.Init() ed.SetDefaultPath("../packages/Default") ed.SetUserPath("../packages/User") ed.SetClipboardFuncs(clipboard.WriteAll, clipboard.ReadAll) // Some packages(e.g Vintageos) need available window and view at start // so we need at least one window and view before loading packages. // Sublime text also has available window view on startup w := ed.NewWindow() w.NewFile() ed.AddPackagesPath("../packages") ed.SetFrontend(f) ed.LogInput(false) ed.LogCommands(false) c := ed.Console() f.Console = newView(c) c.AddObserver(f.Console) c.AddObserver(f) var ( engine *qml.Engine component qml.Object // WaitGroup keeping track of open windows wg sync.WaitGroup ) // create and setup a new engine, destroying // the old one if one exists. // // This is needed to re-load qml files to get // the new file contents from disc as otherwise // the old file would still be what is referenced. newEngine := func() (err error) { if engine != nil { log.Debug("calling destroy") // TODO(.): calling this appears to make the editor *very* crash-prone, just let it leak for now // engine.Destroy() engine = nil } log.Debug("calling newEngine") engine = qml.NewEngine() engine.On("quit", f.Quit) log.Fine("setvar frontend") engine.Context().SetVar("frontend", f) log.Fine("loading %s", qmlWindowFile) component, err = engine.LoadFile(qmlWindowFile) return } if err := newEngine(); err != nil { log.Error("Error on creating new engine: %s", err) panic(err) } addWindow := func(bw *backend.Window) { w := newWindow(bw) f.windows[bw] = w w.launch(&wg, component) } backend.OnNew.Add(f.onNew) backend.OnClose.Add(f.onClose) backend.OnLoad.Add(f.onLoad) backend.OnSelectionModified.Add(f.onSelectionModified) backend.OnNewWindow.Add(addWindow) backend.OnStatusChanged.Add(f.onStatusChanged) // we need to add windows and views that are added before we registered // actions for OnNewWindow and OnNew events for _, w := range ed.Windows() { addWindow(w) for _, v := range w.Views() { f.onNew(v) f.onLoad(v) } } defer func() { fmt.Println(util.Prof) }() // The rest of code is related to livereloading qml files // TODO: this doesnt work currently watch, err := fsnotify.NewWatcher() if err != nil { log.Error("Unable to create file watcher: %s", err) return } defer watch.Close() watch.Add("qml") defer watch.Remove("qml") reloadRequested := false waiting := false go func() { // reloadRequested = true // f.Quit() lastTime := time.Now() for { select { case ev := <-watch.Events: if time.Now().Sub(lastTime) < 1*time.Second { // quitting too frequently causes crashes lastTime = time.Now() continue } if strings.HasSuffix(ev.Name, ".qml") && ev.Op == fsnotify.Write && ev.Op != fsnotify.Chmod && !reloadRequested && waiting { reloadRequested = true f.Quit() lastTime = time.Now() } } } }() for { // Reset reload status reloadRequested = false log.Debug("Waiting for all windows to close") // wg would be the WaitGroup all windows belong to, so first we wait for // all windows to close. waiting = true wg.Wait() waiting = false log.Debug("All windows closed. reloadRequest: %v", reloadRequested) // then we check if there's a reload request in the pipe if !reloadRequested || len(f.windows) == 0 { // This would be a genuine exit; all windows closed by the user break } // *We* closed all windows because we want to reload freshly changed qml // files. for { log.Debug("Calling newEngine") if err := newEngine(); err != nil { // Reset reload status reloadRequested = false waiting = true log.Error(err) for !reloadRequested { // This loop allows us to re-try reloading // if there was an error in the file this time, // we just loop around again when we receive the next // reload request (ie on the next save of the file). time.Sleep(time.Second) } waiting = false continue } log.Debug("break") break } log.Debug("re-launching all windows") // Succeeded loading the file, re-launch all windows for _, w := range f.windows { w.launch(&wg, component) for _, bv := range w.Back().Views() { f.onNew(bv) f.onLoad(bv) } } } return }
func (f *frontend) ErrorMessage(msg string) { log.Error(msg) f.message(msg, criticalIcon, okButton) }
func (t *tbfe) ErrorMessage(msg string) { log.Error(msg) }