diff --git a/cmd/micro/action/actions.go b/cmd/micro/action/actions.go index 3a6e74da..14dded21 100644 --- a/cmd/micro/action/actions.go +++ b/cmd/micro/action/actions.go @@ -17,19 +17,19 @@ import ( // ScrollUp is not an action func (h *BufHandler) ScrollUp(n int) { - v := h.Win.GetView() + v := h.GetView() if v.StartLine >= n { v.StartLine -= n - h.Win.SetView(v) + h.SetView(v) } } // ScrollDown is not an action func (h *BufHandler) ScrollDown(n int) { - v := h.Win.GetView() + v := h.GetView() if v.StartLine <= h.Buf.LinesNum()-1-n { v.StartLine += n - h.Win.SetView(v) + h.SetView(v) } } @@ -38,12 +38,12 @@ func (h *BufHandler) ScrollDown(n int) { func (h *BufHandler) MousePress(e *tcell.EventMouse) bool { b := h.Buf mx, my := e.Position() - mouseLoc := h.Win.GetMouseLoc(buffer.Loc{mx, my}) + mouseLoc := h.GetMouseLoc(buffer.Loc{mx, my}) h.Cursor.Loc = mouseLoc if h.mouseReleased { if b.NumCursors() > 1 { b.ClearCursors() - h.Win.Relocate() + h.Relocate() } if time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) { if h.doubleClick { @@ -104,7 +104,7 @@ func (h *BufHandler) ScrollDownAction() bool { // Center centers the view on the cursor func (h *BufHandler) Center() bool { - v := h.Win.GetView() + v := h.GetView() v.StartLine = h.Cursor.Y - v.Height/2 if v.StartLine+v.Height > h.Buf.LinesNum() { v.StartLine = h.Buf.LinesNum() - v.Height @@ -112,7 +112,7 @@ func (h *BufHandler) Center() bool { if v.StartLine < 0 { v.StartLine = 0 } - h.Win.SetView(v) + h.SetView(v) return true } @@ -826,19 +826,19 @@ func (h *BufHandler) OpenFile() bool { // Start moves the viewport to the start of the buffer func (h *BufHandler) Start() bool { - v := h.Win.GetView() + v := h.GetView() v.StartLine = 0 - h.Win.SetView(v) + h.SetView(v) return false } // End moves the viewport to the end of the buffer func (h *BufHandler) End() bool { // TODO: softwrap problems? - v := h.Win.GetView() + v := h.GetView() if v.Height > h.Buf.LinesNum() { v.StartLine = 0 - h.Win.SetView(v) + h.SetView(v) } else { h.StartLine = h.Buf.LinesNum() - v.Height } @@ -847,19 +847,19 @@ func (h *BufHandler) End() bool { // PageUp scrolls the view up a page func (h *BufHandler) PageUp() bool { - v := h.Win.GetView() + v := h.GetView() if v.StartLine > v.Height { h.ScrollUp(v.Height) } else { v.StartLine = 0 } - h.Win.SetView(v) + h.SetView(v) return false } // PageDown scrolls the view down a page func (h *BufHandler) PageDown() bool { - v := h.Win.GetView() + v := h.GetView() if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height { h.ScrollDown(v.Height) } else if h.Buf.LinesNum() >= v.Height { @@ -873,7 +873,7 @@ func (h *BufHandler) SelectPageUp() bool { if !h.Cursor.HasSelection() { h.Cursor.OrigSelection[0] = h.Cursor.Loc } - h.Cursor.UpN(h.Win.GetView().Height) + h.Cursor.UpN(h.GetView().Height) h.Cursor.SelectTo(h.Cursor.Loc) return true } @@ -883,7 +883,7 @@ func (h *BufHandler) SelectPageDown() bool { if !h.Cursor.HasSelection() { h.Cursor.OrigSelection[0] = h.Cursor.Loc } - h.Cursor.DownN(h.Win.GetView().Height) + h.Cursor.DownN(h.GetView().Height) h.Cursor.SelectTo(h.Cursor.Loc) return true } @@ -897,7 +897,7 @@ func (h *BufHandler) CursorPageUp() bool { h.Cursor.ResetSelection() h.Cursor.StoreVisualX() } - h.Cursor.UpN(h.Win.GetView().Height) + h.Cursor.UpN(h.GetView().Height) return true } @@ -910,25 +910,25 @@ func (h *BufHandler) CursorPageDown() bool { h.Cursor.ResetSelection() h.Cursor.StoreVisualX() } - h.Cursor.DownN(h.Win.GetView().Height) + h.Cursor.DownN(h.GetView().Height) return true } // HalfPageUp scrolls the view up half a page func (h *BufHandler) HalfPageUp() bool { - v := h.Win.GetView() + v := h.GetView() if v.StartLine > v.Height/2 { h.ScrollUp(v.Height / 2) } else { v.StartLine = 0 } - h.Win.SetView(v) + h.SetView(v) return false } // HalfPageDown scrolls the view down half a page func (h *BufHandler) HalfPageDown() bool { - v := h.Win.GetView() + v := h.GetView() if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height/2 { h.ScrollDown(v.Height / 2) } else { @@ -936,7 +936,7 @@ func (h *BufHandler) HalfPageDown() bool { v.StartLine = h.Buf.LinesNum() - v.Height } } - h.Win.SetView(v) + h.SetView(v) return false } @@ -1193,7 +1193,7 @@ func (h *BufHandler) SpawnMultiCursorSelect() bool { func (h *BufHandler) MouseMultiCursor(e *tcell.EventMouse) bool { b := h.Buf mx, my := e.Position() - mouseLoc := h.Win.GetMouseLoc(buffer.Loc{X: mx, Y: my}) + mouseLoc := h.GetMouseLoc(buffer.Loc{X: mx, Y: my}) c := buffer.NewCursor(b, mouseLoc) b.AddCursor(c) b.MergeCursors() @@ -1219,7 +1219,7 @@ func (h *BufHandler) SkipMultiCursor() bool { lastC.Loc = lastC.CurSelection[1] h.Buf.MergeCursors() - h.Win.Relocate() + h.Relocate() } else { InfoBar.Message("No matches found") } diff --git a/cmd/micro/action/bufhandler.go b/cmd/micro/action/bufhandler.go index 4af5be99..153cb166 100644 --- a/cmd/micro/action/bufhandler.go +++ b/cmd/micro/action/bufhandler.go @@ -52,8 +52,9 @@ func BufMapMouse(k MouseEvent, action string) { // The ActionHandler can access the window for necessary info about // visual positions for mouse clicks and scrolling type BufHandler struct { + display.Window + Buf *buffer.Buffer - Win display.Window cursors []*buffer.Cursor Cursor *buffer.Cursor // the active cursor @@ -100,7 +101,7 @@ type BufHandler struct { func NewBufHandler(buf *buffer.Buffer, win display.Window) *BufHandler { h := new(BufHandler) h.Buf = buf - h.Win = win + h.Window = win h.cursors = []*buffer.Cursor{buffer.NewCursor(buf, buf.StartCursor)} h.Cursor = h.cursors[0] @@ -133,7 +134,7 @@ func (h *BufHandler) HandleEvent(event tcell.Event) { // Mouse was just released mx, my := e.Position() - mouseLoc := h.Win.GetMouseLoc(buffer.Loc{X: mx, Y: my}) + mouseLoc := h.GetMouseLoc(buffer.Loc{X: mx, Y: my}) // Relocating here isn't really necessary because the cursor will // be in the right place from the last mouse event @@ -171,14 +172,14 @@ func (h *BufHandler) DoKeyEvent(e KeyEvent) bool { h.Buf.SetCurCursor(c.Num) h.Cursor = c if action(h) { - h.Win.Relocate() + h.Relocate() } } return true } } if action(h) { - h.Win.Relocate() + h.Relocate() } return true } @@ -190,7 +191,7 @@ func (h *BufHandler) DoKeyEvent(e KeyEvent) bool { func (h *BufHandler) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool { if action, ok := BufMouseBindings[e]; ok { if action(h, te) { - h.Win.Relocate() + h.Relocate() } return true } diff --git a/cmd/micro/action/editpane.go b/cmd/micro/action/editpane.go index 54218893..b0bab86c 100644 --- a/cmd/micro/action/editpane.go +++ b/cmd/micro/action/editpane.go @@ -7,6 +7,12 @@ import ( "github.com/zyedidia/micro/cmd/micro/views" ) +type Pane interface { + Handler + display.Window + ID() uint64 +} + type EditPane struct { display.Window *BufHandler diff --git a/cmd/micro/action/events.go b/cmd/micro/action/events.go index 2a366c1d..437a6789 100644 --- a/cmd/micro/action/events.go +++ b/cmd/micro/action/events.go @@ -37,7 +37,5 @@ type MouseAction func(Handler, tcell.EventMouse) bool // A Handler will take a tcell event and execute it // appropriately type Handler interface { - // DoKeyEvent(KeyEvent) bool - // DoMouseEvent(MouseEvent, *tcell.EventMouse) (MouseAction, bool) HandleEvent(tcell.Event) } diff --git a/cmd/micro/action/termhandler.go b/cmd/micro/action/termhandler.go new file mode 100644 index 00000000..34338a81 --- /dev/null +++ b/cmd/micro/action/termhandler.go @@ -0,0 +1,66 @@ +package action + +import ( + "github.com/zyedidia/clipboard" + "github.com/zyedidia/micro/cmd/micro/display" + "github.com/zyedidia/micro/cmd/micro/shell" + "github.com/zyedidia/tcell" + "github.com/zyedidia/terminal" +) + +type TermHandler struct { + *shell.Terminal + display.Window + + mouseReleased bool +} + +// HandleEvent handles a tcell event by forwarding it to the terminal emulator +// If the event is a mouse event and the program running in the emulator +// does not have mouse support, the emulator will support selections and +// copy-paste +func (t *TermHandler) HandleEvent(event tcell.Event) { + if e, ok := event.(*tcell.EventKey); ok { + if t.Status == shell.TTDone { + switch e.Key() { + case tcell.KeyEscape, tcell.KeyCtrlQ, tcell.KeyEnter: + t.Close() + default: + } + } + if e.Key() == tcell.KeyCtrlC && t.HasSelection() { + clipboard.WriteAll(t.GetSelection(t.GetView().Width), "clipboard") + InfoBar.Message("Copied selection to clipboard") + } else if t.Status != shell.TTDone { + t.WriteString(event.EscSeq()) + } + } else if e, ok := event.(*tcell.EventMouse); !ok || t.State.Mode(terminal.ModeMouseMask) { + t.WriteString(event.EscSeq()) + } else { + x, y := e.Position() + v := t.GetView() + x -= v.X + y += v.Y + + if e.Buttons() == tcell.Button1 { + if !t.mouseReleased { + // drag + t.Selection[1].X = x + t.Selection[1].Y = y + } else { + t.Selection[0].X = x + t.Selection[0].Y = y + t.Selection[1].X = x + t.Selection[1].Y = y + } + + t.mouseReleased = false + } else if e.Buttons() == tcell.ButtonNone { + if !t.mouseReleased { + t.Selection[1].X = x + t.Selection[1].Y = y + } + t.mouseReleased = true + } + } +} diff --git a/cmd/micro/display/termwindow.go b/cmd/micro/display/termwindow.go new file mode 100644 index 00000000..e456c45b --- /dev/null +++ b/cmd/micro/display/termwindow.go @@ -0,0 +1,87 @@ +package display + +import ( + "github.com/zyedidia/micro/cmd/micro/buffer" + "github.com/zyedidia/micro/cmd/micro/config" + "github.com/zyedidia/micro/cmd/micro/screen" + "github.com/zyedidia/micro/cmd/micro/shell" + "github.com/zyedidia/tcell" + "github.com/zyedidia/terminal" +) + +type TermWindow struct { + *View + *shell.Terminal + + active bool +} + +func NewTermWindow(x, y, w, h int, term *shell.Terminal) *TermWindow { + tw := new(TermWindow) + tw.Terminal = term + tw.X, tw.Y = x, y + tw.Width, tw.Height = w, h + return tw +} + +// Resize informs the terminal of a resize event +func (w *TermWindow) Resize(width, height int) { + w.Term.Resize(width, height) +} + +func (w *TermWindow) SetActive(b bool) { + w.active = b +} + +func (w *TermWindow) GetMouseLoc(vloc buffer.Loc) buffer.Loc { + return vloc +} + +func (w *TermWindow) Clear() { + for y := 0; y < w.Height; y++ { + for x := 0; x < w.Width; x++ { + screen.Screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle) + } + } +} + +func (w *TermWindow) Relocate() bool { return true } +func (w *TermWindow) GetView() *View { + return w.View +} +func (w *TermWindow) SetView(v *View) { + w.View = v +} + +// Display displays this terminal in a view +func (w *TermWindow) Display() { + w.State.Lock() + defer w.State.Unlock() + + var l buffer.Loc + for y := 0; y < w.Height; y++ { + for x := 0; x < w.Width; x++ { + l.X, l.Y = x, y + c, f, b := w.State.Cell(x, y) + + fg, bg := int(f), int(b) + if f == terminal.DefaultFG { + fg = int(tcell.ColorDefault) + } + if b == terminal.DefaultBG { + bg = int(tcell.ColorDefault) + } + st := tcell.StyleDefault.Foreground(config.GetColor256(int(fg))).Background(config.GetColor256(int(bg))) + + if l.LessThan(w.Selection[1]) && l.GreaterEqual(w.Selection[0]) || l.LessThan(w.Selection[0]) && l.GreaterEqual(w.Selection[1]) { + st = st.Reverse(true) + } + + screen.Screen.SetContent(w.X+x, w.Y+y, c, nil, st) + } + } + if w.State.CursorVisible() && w.active { + curx, cury := w.State.Cursor() + screen.Screen.ShowCursor(curx+w.X, cury+w.Y) + } +} diff --git a/cmd/micro/shell/terminal.go b/cmd/micro/shell/terminal.go new file mode 100644 index 00000000..b4e5da31 --- /dev/null +++ b/cmd/micro/shell/terminal.go @@ -0,0 +1,127 @@ +package shell + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "strconv" + + "github.com/zyedidia/micro/cmd/micro/buffer" + "github.com/zyedidia/micro/cmd/micro/screen" + "github.com/zyedidia/terminal" +) + +type TermType int + +const ( + TTIdle = iota // Waiting for a new command + TTRunning // Currently running a command + TTDone // Finished running a command +) + +// A Terminal holds information for the terminal emulator +type Terminal struct { + State terminal.State + Term *terminal.VT + title string + Status TermType + Selection [2]buffer.Loc + wait bool + getOutput bool + output *bytes.Buffer + callback string +} + +// HasSelection returns whether this terminal has a valid selection +func (t *Terminal) HasSelection() bool { + return t.Selection[0] != t.Selection[1] +} + +// GetSelection returns the selected text +func (t *Terminal) GetSelection(width int) string { + start := t.Selection[0] + end := t.Selection[1] + if start.GreaterThan(end) { + start, end = end, start + } + var ret string + var l buffer.Loc + for y := start.Y; y <= end.Y; y++ { + for x := 0; x < width; x++ { + l.X, l.Y = x, y + if l.GreaterEqual(start) && l.LessThan(end) { + c, _, _ := t.State.Cell(x, y) + ret += string(c) + } + } + } + return ret +} + +// Start begins a new command in this terminal with a given view +func (t *Terminal) Start(execCmd []string, getOutput bool) error { + if len(execCmd) <= 0 { + return nil + } + + cmd := exec.Command(execCmd[0], execCmd[1:]...) + t.output = nil + if getOutput { + t.output = bytes.NewBuffer([]byte{}) + } + Term, _, err := terminal.Start(&t.State, cmd, t.output) + if err != nil { + return err + } + t.Term = Term + t.getOutput = getOutput + t.Status = TTRunning + t.title = execCmd[0] + ":" + strconv.Itoa(cmd.Process.Pid) + + go func() { + for { + err := Term.Parse() + if err != nil { + fmt.Fprintln(os.Stderr, "[Press enter to close]") + break + } + screen.Redraw() + } + // TODO: close Term + // closeterm <- view.Num + }() + + return nil +} + +// Stop stops execution of the terminal and sets the Status +// to TTDone +func (t *Terminal) Stop() { + t.Term.File().Close() + t.Term.Close() + if t.wait { + t.Status = TTDone + } else { + t.Close() + } +} + +// Close sets the Status to TTIdle indicating that the terminal +// is ready for a new command to execute +func (t *Terminal) Close() { + t.Status = TTIdle + // call the lua function that the user has given as a callback + if t.getOutput { + // TODO: plugin callback on Term emulator + // _, err := Call(t.callback, t.output.String()) + // if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") { + // TermMessage(err) + // } + } +} + +// WriteString writes a given string to this terminal's pty +func (t *Terminal) WriteString(str string) { + t.Term.File().WriteString(str) +}