mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-15 21:37:09 +09:00
Add multi cursor support
This commit is contained in:
@@ -985,6 +985,13 @@ func (h *BufHandler) SpawnMultiCursorSelect() bool {
|
|||||||
|
|
||||||
// MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
|
// MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
|
||||||
func (h *BufHandler) MouseMultiCursor(e *tcell.EventMouse) bool {
|
func (h *BufHandler) MouseMultiCursor(e *tcell.EventMouse) bool {
|
||||||
|
b := h.Buf
|
||||||
|
mx, my := e.Position()
|
||||||
|
mouseLoc := h.Win.GetMouseLoc(buffer.Loc{mx, my})
|
||||||
|
c := buffer.NewCursor(b, mouseLoc)
|
||||||
|
b.AddCursor(c)
|
||||||
|
b.MergeCursors()
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -995,10 +1002,15 @@ func (h *BufHandler) SkipMultiCursor() bool {
|
|||||||
|
|
||||||
// RemoveMultiCursor removes the latest multiple cursor
|
// RemoveMultiCursor removes the latest multiple cursor
|
||||||
func (h *BufHandler) RemoveMultiCursor() bool {
|
func (h *BufHandler) RemoveMultiCursor() bool {
|
||||||
|
if h.Buf.NumCursors() > 1 {
|
||||||
|
h.Buf.RemoveCursor(h.Buf.NumCursors() - 1)
|
||||||
|
h.Buf.UpdateCursors()
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveAllMultiCursors removes all cursors except the base cursor
|
// RemoveAllMultiCursors removes all cursors except the base cursor
|
||||||
func (h *BufHandler) RemoveAllMultiCursors() bool {
|
func (h *BufHandler) RemoveAllMultiCursors() bool {
|
||||||
return false
|
h.Buf.ClearCursors()
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,15 +13,18 @@ type BufKeyAction func(*BufHandler) bool
|
|||||||
type BufMouseAction func(*BufHandler, *tcell.EventMouse) bool
|
type BufMouseAction func(*BufHandler, *tcell.EventMouse) bool
|
||||||
|
|
||||||
var BufKeyBindings map[KeyEvent]BufKeyAction
|
var BufKeyBindings map[KeyEvent]BufKeyAction
|
||||||
|
var BufKeyStrings map[KeyEvent]string
|
||||||
var BufMouseBindings map[MouseEvent]BufMouseAction
|
var BufMouseBindings map[MouseEvent]BufMouseAction
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
BufKeyBindings = make(map[KeyEvent]BufKeyAction)
|
BufKeyBindings = make(map[KeyEvent]BufKeyAction)
|
||||||
|
BufKeyStrings = make(map[KeyEvent]string)
|
||||||
BufMouseBindings = make(map[MouseEvent]BufMouseAction)
|
BufMouseBindings = make(map[MouseEvent]BufMouseAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BufMapKey(k KeyEvent, action string) {
|
func BufMapKey(k KeyEvent, action string) {
|
||||||
if f, ok := BufKeyActions[action]; ok {
|
if f, ok := BufKeyActions[action]; ok {
|
||||||
|
BufKeyStrings[k] = action
|
||||||
BufKeyBindings[k] = f
|
BufKeyBindings[k] = f
|
||||||
} else {
|
} else {
|
||||||
util.TermMessage("Error:", action, "does not exist")
|
util.TermMessage("Error:", action, "does not exist")
|
||||||
@@ -106,16 +109,11 @@ func (h *BufHandler) HandleEvent(event tcell.Event) {
|
|||||||
mod: e.Modifiers(),
|
mod: e.Modifiers(),
|
||||||
r: e.Rune(),
|
r: e.Rune(),
|
||||||
}
|
}
|
||||||
cursors := h.Buf.GetCursors()
|
|
||||||
for _, c := range cursors {
|
|
||||||
h.Buf.SetCurCursor(c.Num)
|
|
||||||
h.Cursor = c
|
|
||||||
done := h.DoKeyEvent(ke)
|
done := h.DoKeyEvent(ke)
|
||||||
if !done && e.Key() == tcell.KeyRune {
|
if !done && e.Key() == tcell.KeyRune {
|
||||||
h.DoRuneInsert(e.Rune())
|
h.DoRuneInsert(e.Rune())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// TODO: maybe reset curcursor to 0
|
|
||||||
case *tcell.EventMouse:
|
case *tcell.EventMouse:
|
||||||
switch e.Buttons() {
|
switch e.Buttons() {
|
||||||
case tcell.ButtonNone:
|
case tcell.ButtonNone:
|
||||||
@@ -145,17 +143,25 @@ func (h *BufHandler) HandleEvent(event tcell.Event) {
|
|||||||
btn: e.Buttons(),
|
btn: e.Buttons(),
|
||||||
mod: e.Modifiers(),
|
mod: e.Modifiers(),
|
||||||
}
|
}
|
||||||
cursors := h.Buf.GetCursors()
|
|
||||||
for _, c := range cursors {
|
|
||||||
h.Buf.SetCurCursor(c.Num)
|
|
||||||
h.Cursor = c
|
|
||||||
h.DoMouseEvent(me, e)
|
h.DoMouseEvent(me, e)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *BufHandler) DoKeyEvent(e KeyEvent) bool {
|
func (h *BufHandler) DoKeyEvent(e KeyEvent) bool {
|
||||||
if action, ok := BufKeyBindings[e]; ok {
|
if action, ok := BufKeyBindings[e]; ok {
|
||||||
|
for _, a := range MultiActions {
|
||||||
|
if a == BufKeyStrings[e] {
|
||||||
|
cursors := h.Buf.GetCursors()
|
||||||
|
for _, c := range cursors {
|
||||||
|
h.Buf.SetCurCursor(c.Num)
|
||||||
|
h.Cursor = c
|
||||||
|
if action(h) {
|
||||||
|
h.Win.Relocate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
if action(h) {
|
if action(h) {
|
||||||
h.Win.Relocate()
|
h.Win.Relocate()
|
||||||
}
|
}
|
||||||
@@ -175,18 +181,21 @@ func (h *BufHandler) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *BufHandler) DoRuneInsert(r rune) {
|
func (h *BufHandler) DoRuneInsert(r rune) {
|
||||||
|
cursors := h.Buf.GetCursors()
|
||||||
|
for _, c := range cursors {
|
||||||
// Insert a character
|
// Insert a character
|
||||||
if h.Cursor.HasSelection() {
|
if c.HasSelection() {
|
||||||
h.Cursor.DeleteSelection()
|
c.DeleteSelection()
|
||||||
h.Cursor.ResetSelection()
|
c.ResetSelection()
|
||||||
}
|
}
|
||||||
|
|
||||||
if h.isOverwriteMode {
|
if h.isOverwriteMode {
|
||||||
next := h.Cursor.Loc
|
next := c.Loc
|
||||||
next.X++
|
next.X++
|
||||||
h.Buf.Replace(h.Cursor.Loc, next, string(r))
|
h.Buf.Replace(c.Loc, next, string(r))
|
||||||
} else {
|
} else {
|
||||||
h.Buf.Insert(h.Cursor.Loc, string(r))
|
h.Buf.Insert(c.Loc, string(r))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,3 +301,60 @@ var BufMouseActions = map[string]BufMouseAction{
|
|||||||
"MousePress": (*BufHandler).MousePress,
|
"MousePress": (*BufHandler).MousePress,
|
||||||
"MouseMultiCursor": (*BufHandler).MouseMultiCursor,
|
"MouseMultiCursor": (*BufHandler).MouseMultiCursor,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const funcPrefixLen = 21 // length of "action.(*BufHandler)."
|
||||||
|
|
||||||
|
// MultiActions is a list of actions that should be executed multiple
|
||||||
|
// times if there are multiple cursors (one per cursor)
|
||||||
|
// Generally actions that modify global editor state like quitting or
|
||||||
|
// saving should not be included in this list
|
||||||
|
var MultiActions = []string{
|
||||||
|
"CursorUp",
|
||||||
|
"CursorDown",
|
||||||
|
"CursorPageUp",
|
||||||
|
"CursorPageDown",
|
||||||
|
"CursorLeft",
|
||||||
|
"CursorRight",
|
||||||
|
"CursorStart",
|
||||||
|
"CursorEnd",
|
||||||
|
"SelectToStart",
|
||||||
|
"SelectToEnd",
|
||||||
|
"SelectUp",
|
||||||
|
"SelectDown",
|
||||||
|
"SelectLeft",
|
||||||
|
"SelectRight",
|
||||||
|
"WordRight",
|
||||||
|
"WordLeft",
|
||||||
|
"SelectWordRight",
|
||||||
|
"SelectWordLeft",
|
||||||
|
"DeleteWordRight",
|
||||||
|
"DeleteWordLeft",
|
||||||
|
"SelectLine",
|
||||||
|
"SelectToStartOfLine",
|
||||||
|
"SelectToEndOfLine",
|
||||||
|
"ParagraphPrevious",
|
||||||
|
"ParagraphNext",
|
||||||
|
"InsertNewline",
|
||||||
|
"InsertSpace",
|
||||||
|
"Backspace",
|
||||||
|
"Delete",
|
||||||
|
"InsertTab",
|
||||||
|
"FindNext",
|
||||||
|
"FindPrevious",
|
||||||
|
"Cut",
|
||||||
|
"CutLine",
|
||||||
|
"DuplicateLine",
|
||||||
|
"DeleteLine",
|
||||||
|
"MoveLinesUp",
|
||||||
|
"MoveLinesDown",
|
||||||
|
"IndentSelection",
|
||||||
|
"OutdentSelection",
|
||||||
|
"OutdentLine",
|
||||||
|
"Paste",
|
||||||
|
"PastePrimary",
|
||||||
|
"SelectPageUp",
|
||||||
|
"SelectPageDown",
|
||||||
|
"StartOfLine",
|
||||||
|
"EndOfLine",
|
||||||
|
"JumpToMatchingBrace",
|
||||||
|
}
|
||||||
|
|||||||
@@ -236,36 +236,6 @@ func (b *Buffer) ReOpen() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCursors resets this buffer's cursors to a new list
|
|
||||||
func (b *Buffer) SetCursors(c []*Cursor) {
|
|
||||||
b.cursors = c
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCurCursor sets the current cursor
|
|
||||||
func (b *Buffer) SetCurCursor(n int) {
|
|
||||||
b.curCursor = n
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetActiveCursor returns the main cursor in this buffer
|
|
||||||
func (b *Buffer) GetActiveCursor() *Cursor {
|
|
||||||
return b.cursors[b.curCursor]
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCursor returns the nth cursor
|
|
||||||
func (b *Buffer) GetCursor(n int) *Cursor {
|
|
||||||
return b.cursors[n]
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCursors returns the list of cursors in this buffer
|
|
||||||
func (b *Buffer) GetCursors() []*Cursor {
|
|
||||||
return b.cursors
|
|
||||||
}
|
|
||||||
|
|
||||||
// NumCursors returns the number of cursors
|
|
||||||
func (b *Buffer) NumCursors() int {
|
|
||||||
return len(b.cursors)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LineBytes returns line n as an array of bytes
|
// LineBytes returns line n as an array of bytes
|
||||||
func (b *Buffer) LineBytes(n int) []byte {
|
func (b *Buffer) LineBytes(n int) []byte {
|
||||||
if n >= len(b.lines) || n < 0 {
|
if n >= len(b.lines) || n < 0 {
|
||||||
@@ -429,6 +399,42 @@ func (b *Buffer) IndentString(tabsize int) string {
|
|||||||
return "\t"
|
return "\t"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCursors resets this buffer's cursors to a new list
|
||||||
|
func (b *Buffer) SetCursors(c []*Cursor) {
|
||||||
|
b.cursors = c
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCursor adds a new cursor to the list
|
||||||
|
func (b *Buffer) AddCursor(c *Cursor) {
|
||||||
|
b.cursors = append(b.cursors, c)
|
||||||
|
b.UpdateCursors()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCurCursor sets the current cursor
|
||||||
|
func (b *Buffer) SetCurCursor(n int) {
|
||||||
|
b.curCursor = n
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetActiveCursor returns the main cursor in this buffer
|
||||||
|
func (b *Buffer) GetActiveCursor() *Cursor {
|
||||||
|
return b.cursors[b.curCursor]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCursor returns the nth cursor
|
||||||
|
func (b *Buffer) GetCursor(n int) *Cursor {
|
||||||
|
return b.cursors[n]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCursors returns the list of cursors in this buffer
|
||||||
|
func (b *Buffer) GetCursors() []*Cursor {
|
||||||
|
return b.cursors
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumCursors returns the number of cursors
|
||||||
|
func (b *Buffer) NumCursors() int {
|
||||||
|
return len(b.cursors)
|
||||||
|
}
|
||||||
|
|
||||||
// MergeCursors merges any cursors that are at the same position
|
// MergeCursors merges any cursors that are at the same position
|
||||||
// into one cursor
|
// into one cursor
|
||||||
func (b *Buffer) MergeCursors() {
|
func (b *Buffer) MergeCursors() {
|
||||||
@@ -464,6 +470,12 @@ func (b *Buffer) UpdateCursors() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Buffer) RemoveCursor(i int) {
|
||||||
|
copy(b.cursors[i:], b.cursors[i+1:])
|
||||||
|
b.cursors[len(b.cursors)-1] = nil
|
||||||
|
b.cursors = b.cursors[:len(b.cursors)-1]
|
||||||
|
}
|
||||||
|
|
||||||
// ClearCursors removes all extra cursors
|
// ClearCursors removes all extra cursors
|
||||||
func (b *Buffer) ClearCursors() {
|
func (b *Buffer) ClearCursors() {
|
||||||
for i := 1; i < len(b.cursors); i++ {
|
for i := 1; i < len(b.cursors); i++ {
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc, r rune) (tcell.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *BufWindow) showCursor(x, y int, main bool) {
|
func (w *BufWindow) showCursor(x, y int, main bool) {
|
||||||
if main {
|
if !main {
|
||||||
screen.Screen.ShowCursor(x, y)
|
screen.Screen.ShowCursor(x, y)
|
||||||
} else {
|
} else {
|
||||||
r, _, _, _ := screen.Screen.GetContent(x, y)
|
r, _, _, _ := screen.Screen.GetContent(x, y)
|
||||||
@@ -370,15 +370,18 @@ func (w *BufWindow) displayBuffer() {
|
|||||||
// this represents the current draw position in the buffer (char positions)
|
// this represents the current draw position in the buffer (char positions)
|
||||||
bloc := buffer.Loc{X: -1, Y: w.StartLine}
|
bloc := buffer.Loc{X: -1, Y: w.StartLine}
|
||||||
|
|
||||||
activeC := b.GetActiveCursor()
|
cursors := b.GetCursors()
|
||||||
|
|
||||||
curStyle := config.DefStyle
|
curStyle := config.DefStyle
|
||||||
for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
|
for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
|
||||||
vloc.X = 0
|
vloc.X = 0
|
||||||
if b.Settings["ruler"].(bool) {
|
if b.Settings["ruler"].(bool) {
|
||||||
s := lineNumStyle
|
s := lineNumStyle
|
||||||
if bloc.Y == activeC.Y {
|
for _, c := range cursors {
|
||||||
|
if bloc.Y == c.Y {
|
||||||
s = curNumStyle
|
s = curNumStyle
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
w.drawLineNum(s, false, maxLineNumLength, &vloc, &bloc)
|
w.drawLineNum(s, false, maxLineNumLength, &vloc, &bloc)
|
||||||
}
|
}
|
||||||
@@ -391,11 +394,12 @@ func (w *BufWindow) displayBuffer() {
|
|||||||
}
|
}
|
||||||
bloc.X = bslice
|
bloc.X = bslice
|
||||||
|
|
||||||
draw := func(r rune, style tcell.Style) {
|
draw := func(r rune, style tcell.Style, showcursor bool) {
|
||||||
if nColsBeforeStart <= 0 {
|
if nColsBeforeStart <= 0 {
|
||||||
if activeC.HasSelection() &&
|
for _, c := range cursors {
|
||||||
(bloc.GreaterEqual(activeC.CurSelection[0]) && bloc.LessThan(activeC.CurSelection[1]) ||
|
if c.HasSelection() &&
|
||||||
bloc.LessThan(activeC.CurSelection[0]) && bloc.GreaterEqual(activeC.CurSelection[1])) {
|
(bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) ||
|
||||||
|
bloc.LessThan(c.CurSelection[0]) && bloc.GreaterEqual(c.CurSelection[1])) {
|
||||||
// The current character is selected
|
// The current character is selected
|
||||||
style = config.DefStyle.Reverse(true)
|
style = config.DefStyle.Reverse(true)
|
||||||
|
|
||||||
@@ -405,14 +409,23 @@ func (w *BufWindow) displayBuffer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if b.Settings["cursorline"].(bool) &&
|
if b.Settings["cursorline"].(bool) &&
|
||||||
!activeC.HasSelection() && activeC.Y == bloc.Y {
|
!c.HasSelection() && c.Y == bloc.Y {
|
||||||
if s, ok := config.Colorscheme["cursor-line"]; ok {
|
if s, ok := config.Colorscheme["cursor-line"]; ok {
|
||||||
fg, _, _ := s.Decompose()
|
fg, _, _ := s.Decompose()
|
||||||
style = style.Background(fg)
|
style = style.Background(fg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, nil, style)
|
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, nil, style)
|
||||||
|
|
||||||
|
if showcursor {
|
||||||
|
for _, c := range cursors {
|
||||||
|
if c.X == bloc.X && c.Y == bloc.Y {
|
||||||
|
w.showCursor(w.X+vloc.X, w.Y+vloc.Y, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
vloc.X++
|
vloc.X++
|
||||||
}
|
}
|
||||||
nColsBeforeStart--
|
nColsBeforeStart--
|
||||||
@@ -422,14 +435,10 @@ func (w *BufWindow) displayBuffer() {
|
|||||||
|
|
||||||
totalwidth := w.StartCol - nColsBeforeStart
|
totalwidth := w.StartCol - nColsBeforeStart
|
||||||
for len(line) > 0 {
|
for len(line) > 0 {
|
||||||
if activeC.X == bloc.X && activeC.Y == bloc.Y {
|
|
||||||
w.showCursor(vloc.X, vloc.Y, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, size := utf8.DecodeRune(line)
|
r, size := utf8.DecodeRune(line)
|
||||||
curStyle, _ = w.getStyle(curStyle, bloc, r)
|
curStyle, _ = w.getStyle(curStyle, bloc, r)
|
||||||
|
|
||||||
draw(r, curStyle)
|
draw(r, curStyle, true)
|
||||||
|
|
||||||
width := 0
|
width := 0
|
||||||
|
|
||||||
@@ -443,15 +452,15 @@ func (w *BufWindow) displayBuffer() {
|
|||||||
char = '@'
|
char = '@'
|
||||||
}
|
}
|
||||||
|
|
||||||
bloc.X++
|
|
||||||
line = line[size:]
|
|
||||||
|
|
||||||
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
|
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
|
||||||
if width > 1 {
|
if width > 1 {
|
||||||
for i := 1; i < width; i++ {
|
for i := 1; i < width; i++ {
|
||||||
draw(char, curStyle)
|
draw(char, curStyle, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bloc.X++
|
||||||
|
line = line[size:]
|
||||||
|
|
||||||
totalwidth += width
|
totalwidth += width
|
||||||
|
|
||||||
// If we reach the end of the window then we either stop or we wrap for softwrap
|
// If we reach the end of the window then we either stop or we wrap for softwrap
|
||||||
@@ -470,12 +479,10 @@ func (w *BufWindow) displayBuffer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if activeC.X == bloc.X && activeC.Y == bloc.Y {
|
|
||||||
w.showCursor(vloc.X, vloc.Y, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for _, c := range cursors {
|
||||||
if b.Settings["cursorline"].(bool) &&
|
if b.Settings["cursorline"].(bool) &&
|
||||||
!activeC.HasSelection() && activeC.Y == bloc.Y {
|
!c.HasSelection() && c.Y == bloc.Y {
|
||||||
style := config.DefStyle
|
style := config.DefStyle
|
||||||
if s, ok := config.Colorscheme["cursor-line"]; ok {
|
if s, ok := config.Colorscheme["cursor-line"]; ok {
|
||||||
fg, _, _ := s.Decompose()
|
fg, _, _ := s.Decompose()
|
||||||
@@ -485,6 +492,13 @@ func (w *BufWindow) displayBuffer() {
|
|||||||
screen.Screen.SetContent(i, vloc.Y, ' ', nil, style)
|
screen.Screen.SetContent(i, vloc.Y, ' ', nil, style)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cursors {
|
||||||
|
if c.X == bloc.X && c.Y == bloc.Y {
|
||||||
|
w.showCursor(w.X+vloc.X, w.Y+vloc.Y, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bloc.X = w.StartCol
|
bloc.X = w.StartCol
|
||||||
bloc.Y++
|
bloc.Y++
|
||||||
|
|||||||
Reference in New Issue
Block a user