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) } } }
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) loop() { timechan := make(chan bool, 0) // Only set up the timers if we should actually blink the cursor // This should somehow be changeable on an OnSettingsChanged callback if p, _ := t.editor.Settings().Get("caret_blink", true).(bool); p { duration := time.Second / 2 if p, ok := t.editor.Settings().Get("caret_blink_phase", 1.0).(float64); ok { duration = time.Duration(float64(time.Second)*p) / 2 } timer := time.NewTimer(duration) defer func() { timer.Stop() close(timechan) }() go func() { for range timer.C { timechan <- true timer.Reset(duration) } }() } // Due to termbox still running, we can't close evchan evchan := make(chan termbox.Event, 32) go func() { for { evchan <- termbox.PollEvent() } }() for { p := util.Prof.Enter("mainloop") select { case ev := <-evchan: mp := util.Prof.Enter("evchan") switch ev.Type { case termbox.EventError: log.Debug("error occured") return case termbox.EventResize: t.handleResize(ev.Height, ev.Width, false) case termbox.EventKey: t.handleInput(ev) blink = false } mp.Exit() case <-timechan: blink = !blink t.render() case <-t.shutdown: return } p.Exit() } }
func (f *frontend) HandleInput(text string, keycode int, modifiers int) bool { log.Debug("frontend.HandleInput: text=%v, key=%x, modifiers=%x", text, keycode, modifiers) shift := false alt := false ctrl := false super := false if key, ok := lut[keycode]; ok { ed := backend.GetEditor() if modifiers&shift_mod != 0 { shift = true } if modifiers&alt_mod != 0 { alt = true } if modifiers&ctrl_mod != 0 { if runtime.GOOS == "darwin" { super = true } else { ctrl = true } } if modifiers&meta_mod != 0 { if runtime.GOOS == "darwin" { ctrl = true } else { super = true } } ed.HandleInput(keys.KeyPress{Text: text, Key: key, Shift: shift, Alt: alt, Ctrl: ctrl, Super: super}) return true } return false }
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 }