mirror of
https://github.com/zyedidia/micro.git
synced 2026-02-12 18:10:26 +09:00
Implement searching
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -560,6 +561,37 @@ func (h *BufHandler) SaveAs() bool {
|
||||
|
||||
// Find opens a prompt and searches forward for the input
|
||||
func (h *BufHandler) Find() bool {
|
||||
InfoBar.Prompt("Find: ", "", func(resp string) {
|
||||
match, found, _ := h.Buf.FindNext(resp, h.Cursor.Loc, true)
|
||||
if found {
|
||||
h.Cursor.SetSelectionStart(match[0])
|
||||
h.Cursor.SetSelectionEnd(match[1])
|
||||
h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
|
||||
h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
|
||||
} else {
|
||||
log.Println("RESET")
|
||||
h.Cursor.ResetSelection()
|
||||
}
|
||||
}, func(resp string, canceled bool) {
|
||||
if !canceled {
|
||||
match, found, err := h.Buf.FindNext(resp, h.Cursor.Loc, true)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
if found {
|
||||
h.Cursor.SetSelectionStart(match[0])
|
||||
h.Cursor.SetSelectionEnd(match[1])
|
||||
h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
|
||||
h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
|
||||
h.Cursor.Loc = h.Cursor.CurSelection[1]
|
||||
} else {
|
||||
h.Cursor.ResetSelection()
|
||||
}
|
||||
} else {
|
||||
h.Cursor.ResetSelection()
|
||||
}
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -725,7 +757,7 @@ func (h *BufHandler) SelectAll() bool {
|
||||
|
||||
// OpenFile opens a new file in the buffer
|
||||
func (h *BufHandler) OpenFile() bool {
|
||||
InfoBar.Prompt("> ", "open ", func(resp string, canceled bool) {
|
||||
InfoBar.Prompt("> ", "open ", nil, func(resp string, canceled bool) {
|
||||
if !canceled {
|
||||
HandleCommand(resp)
|
||||
}
|
||||
@@ -889,7 +921,7 @@ func (h *BufHandler) ShellMode() bool {
|
||||
|
||||
// CommandMode lets the user enter a command
|
||||
func (h *BufHandler) CommandMode() bool {
|
||||
InfoBar.Prompt("> ", "", func(resp string, canceled bool) {
|
||||
InfoBar.Prompt("> ", "", nil, func(resp string, canceled bool) {
|
||||
if !canceled {
|
||||
HandleCommand(resp)
|
||||
}
|
||||
|
||||
@@ -103,7 +103,8 @@ modSearch:
|
||||
k = string(unicode.ToUpper(rune(k[0]))) + k[1:]
|
||||
if code, ok := keyEvents["Ctrl"+k]; ok {
|
||||
var r tcell.Key
|
||||
if code < 256 {
|
||||
// Special case for escape, for some reason tcell doesn't send it with the esc character
|
||||
if code < 256 && code != 27 {
|
||||
r = code
|
||||
}
|
||||
// It is, we're done.
|
||||
@@ -118,7 +119,8 @@ modSearch:
|
||||
// See if we can find the key in bindingKeys
|
||||
if code, ok := keyEvents[k]; ok {
|
||||
var r tcell.Key
|
||||
if code < 256 {
|
||||
// Special case for escape, for some reason tcell doesn't send it with the esc character
|
||||
if code < 256 && code != 27 {
|
||||
r = code
|
||||
}
|
||||
return KeyEvent{
|
||||
|
||||
@@ -22,6 +22,7 @@ func init() {
|
||||
BufMouseBindings = make(map[MouseEvent]BufMouseAction)
|
||||
}
|
||||
|
||||
// BufMapKey maps a key event to an action
|
||||
func BufMapKey(k KeyEvent, action string) {
|
||||
if f, ok := BufKeyActions[action]; ok {
|
||||
BufKeyStrings[k] = action
|
||||
@@ -30,6 +31,8 @@ func BufMapKey(k KeyEvent, action string) {
|
||||
util.TermMessage("Error:", action, "does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
// BufMapMouse maps a mouse event to an action
|
||||
func BufMapMouse(k MouseEvent, action string) {
|
||||
if f, ok := BufMouseActions[action]; ok {
|
||||
BufMouseBindings[k] = f
|
||||
@@ -147,10 +150,13 @@ func (h *BufHandler) HandleEvent(event tcell.Event) {
|
||||
}
|
||||
}
|
||||
|
||||
// DoKeyEvent executes a key event by finding the action it is bound
|
||||
// to and executing it (possibly multiple times for multiple cursors)
|
||||
func (h *BufHandler) DoKeyEvent(e KeyEvent) bool {
|
||||
if action, ok := BufKeyBindings[e]; ok {
|
||||
for _, a := range MultiActions {
|
||||
if a == BufKeyStrings[e] {
|
||||
estr := BufKeyStrings[e]
|
||||
for _, s := range MultiActions {
|
||||
if s == estr {
|
||||
cursors := h.Buf.GetCursors()
|
||||
for _, c := range cursors {
|
||||
h.Buf.SetCurCursor(c.Num)
|
||||
@@ -170,6 +176,8 @@ func (h *BufHandler) DoKeyEvent(e KeyEvent) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// DoMouseEvent executes a mouse event by finding the action it is bound
|
||||
// to and executing it
|
||||
func (h *BufHandler) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
|
||||
if action, ok := BufMouseBindings[e]; ok {
|
||||
if action(h, te) {
|
||||
@@ -180,6 +188,8 @@ func (h *BufHandler) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// DoRuneInsert inserts a given rune into the current buffer
|
||||
// (possibly multiple times for multiple cursors)
|
||||
func (h *BufHandler) DoRuneInsert(r rune) {
|
||||
cursors := h.Buf.GetCursors()
|
||||
for _, c := range cursors {
|
||||
@@ -199,6 +209,7 @@ func (h *BufHandler) DoRuneInsert(r rune) {
|
||||
}
|
||||
}
|
||||
|
||||
// BufKeyActions contains the list of all possible key actions the bufhandler could execute
|
||||
var BufKeyActions = map[string]BufKeyAction{
|
||||
"CursorUp": (*BufHandler).CursorUp,
|
||||
"CursorDown": (*BufHandler).CursorDown,
|
||||
@@ -297,13 +308,13 @@ var BufKeyActions = map[string]BufKeyAction{
|
||||
// This was changed to InsertNewline but I don't want to break backwards compatibility
|
||||
"InsertEnter": (*BufHandler).InsertNewline,
|
||||
}
|
||||
|
||||
// BufMouseActions contains the list of all possible mouse actions the bufhandler could execute
|
||||
var BufMouseActions = map[string]BufMouseAction{
|
||||
"MousePress": (*BufHandler).MousePress,
|
||||
"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
|
||||
|
||||
@@ -119,7 +119,7 @@ func DefaultCommands() map[string]StrCommand {
|
||||
// enter
|
||||
func CommandEditAction(prompt string) BufKeyAction {
|
||||
return func(h *BufHandler) bool {
|
||||
InfoBar.Prompt("> ", prompt, func(resp string, canceled bool) {
|
||||
InfoBar.Prompt("> ", prompt, nil, func(resp string, canceled bool) {
|
||||
if !canceled {
|
||||
HandleCommand(resp)
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
|
||||
type EditPane struct {
|
||||
display.Window
|
||||
Handler
|
||||
*BufHandler
|
||||
}
|
||||
|
||||
type InfoPane struct {
|
||||
display.Window
|
||||
Handler
|
||||
*InfoHandler
|
||||
*info.InfoBuf
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ func NewBufEditPane(x, y, width, height int, b *buffer.Buffer) *EditPane {
|
||||
// TODO: can probably replace editpane with bufhandler entirely
|
||||
w := display.NewBufWindow(x, y, width, height, b)
|
||||
e.Window = w
|
||||
e.Handler = NewBufHandler(b, w)
|
||||
e.BufHandler = NewBufHandler(b, w)
|
||||
|
||||
return e
|
||||
}
|
||||
@@ -32,7 +32,7 @@ func NewInfoBar() *InfoPane {
|
||||
ib := info.NewBuffer()
|
||||
w := display.NewInfoWindow(ib)
|
||||
e.Window = w
|
||||
e.Handler = NewBufHandler(ib.Buffer, w)
|
||||
e.InfoHandler = NewInfoHandler(ib, w)
|
||||
e.InfoBuf = ib
|
||||
|
||||
return e
|
||||
|
||||
158
cmd/micro/action/infohandler.go
Normal file
158
cmd/micro/action/infohandler.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/micro/cmd/micro/display"
|
||||
"github.com/zyedidia/micro/cmd/micro/info"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
type InfoKeyAction func(*InfoHandler)
|
||||
|
||||
type InfoHandler struct {
|
||||
*BufHandler
|
||||
*info.InfoBuf
|
||||
}
|
||||
|
||||
func NewInfoHandler(ib *info.InfoBuf, w display.Window) *InfoHandler {
|
||||
ih := new(InfoHandler)
|
||||
ih.InfoBuf = ib
|
||||
ih.BufHandler = NewBufHandler(ib.Buffer, w)
|
||||
|
||||
return ih
|
||||
}
|
||||
|
||||
func (h *InfoHandler) HandleEvent(event tcell.Event) {
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventKey:
|
||||
ke := KeyEvent{
|
||||
code: e.Key(),
|
||||
mod: e.Modifiers(),
|
||||
r: e.Rune(),
|
||||
}
|
||||
|
||||
done := h.DoKeyEvent(ke)
|
||||
if !done && e.Key() == tcell.KeyRune {
|
||||
h.DoRuneInsert(e.Rune())
|
||||
}
|
||||
case *tcell.EventMouse:
|
||||
h.BufHandler.HandleEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *InfoHandler) DoKeyEvent(e KeyEvent) bool {
|
||||
done := false
|
||||
if action, ok := BufKeyBindings[e]; ok {
|
||||
estr := BufKeyStrings[e]
|
||||
for _, s := range InfoNones {
|
||||
if s == estr {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for s, a := range InfoOverrides {
|
||||
if s == estr {
|
||||
done = true
|
||||
a(h)
|
||||
break
|
||||
}
|
||||
}
|
||||
if !done {
|
||||
done = action(h.BufHandler)
|
||||
}
|
||||
}
|
||||
if done && h.EventCallback != nil {
|
||||
h.EventCallback(strings.TrimSpace(string(h.LineBytes(0))))
|
||||
}
|
||||
return done
|
||||
}
|
||||
|
||||
func (h *InfoHandler) DoRuneInsert(r rune) {
|
||||
h.BufHandler.DoRuneInsert(r)
|
||||
if h.EventCallback != nil {
|
||||
h.EventCallback(strings.TrimSpace(string(h.LineBytes(0))))
|
||||
}
|
||||
}
|
||||
|
||||
// InfoNones is a list of actions that should have no effect when executed
|
||||
// by an infohandler
|
||||
var InfoNones = []string{
|
||||
"Save",
|
||||
"SaveAll",
|
||||
"SaveAs",
|
||||
"Find",
|
||||
"FindNext",
|
||||
"FindPrevious",
|
||||
"Center",
|
||||
"DuplicateLine",
|
||||
"MoveLinesUp",
|
||||
"MoveLinesDown",
|
||||
"OpenFile",
|
||||
"Start",
|
||||
"End",
|
||||
"PageUp",
|
||||
"PageDown",
|
||||
"SelectPageUp",
|
||||
"SelectPageDown",
|
||||
"HalfPageUp",
|
||||
"HalfPageDown",
|
||||
"ToggleHelp",
|
||||
"ToggleKeyMenu",
|
||||
"ToggleRuler",
|
||||
"JumpLine",
|
||||
"ClearStatus",
|
||||
"ShellMode",
|
||||
"CommandMode",
|
||||
"AddTab",
|
||||
"PreviousTab",
|
||||
"NextTab",
|
||||
"NextSplit",
|
||||
"PreviousSplit",
|
||||
"Unsplit",
|
||||
"VSplit",
|
||||
"HSplit",
|
||||
"ToggleMacro",
|
||||
"PlayMacro",
|
||||
"Suspend",
|
||||
"ScrollUp",
|
||||
"ScrollDown",
|
||||
"SpawnMultiCursor",
|
||||
"SpawnMultiCursorSelect",
|
||||
"RemoveMultiCursor",
|
||||
"RemoveAllMultiCursors",
|
||||
"SkipMultiCursor",
|
||||
}
|
||||
|
||||
// InfoOverrides is the list of actions which have been overriden
|
||||
// by the infohandler
|
||||
var InfoOverrides = map[string]InfoKeyAction{
|
||||
"CursorUp": (*InfoHandler).CursorUp,
|
||||
"CursorDown": (*InfoHandler).CursorDown,
|
||||
"InsertNewline": (*InfoHandler).InsertNewline,
|
||||
"InsertTab": (*InfoHandler).InsertTab,
|
||||
"Escape": (*InfoHandler).Escape,
|
||||
"Quit": (*InfoHandler).Quit,
|
||||
"QuitAll": (*InfoHandler).QuitAll,
|
||||
}
|
||||
|
||||
func (h *InfoHandler) CursorUp() {
|
||||
// TODO: history
|
||||
}
|
||||
func (h *InfoHandler) CursorDown() {
|
||||
// TODO: history
|
||||
}
|
||||
func (h *InfoHandler) InsertTab() {
|
||||
// TODO: autocomplete
|
||||
}
|
||||
func (h *InfoHandler) InsertNewline() {
|
||||
h.DonePrompt(false)
|
||||
}
|
||||
func (h *InfoHandler) Quit() {
|
||||
h.DonePrompt(true)
|
||||
}
|
||||
func (h *InfoHandler) QuitAll() {
|
||||
h.DonePrompt(true)
|
||||
}
|
||||
func (h *InfoHandler) Escape() {
|
||||
h.DonePrompt(true)
|
||||
}
|
||||
93
cmd/micro/buffer/search.go
Normal file
93
cmd/micro/buffer/search.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/zyedidia/micro/cmd/micro/util"
|
||||
)
|
||||
|
||||
func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
||||
start.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)
|
||||
|
||||
for i := start.Y; i <= end.Y; i++ {
|
||||
l := b.LineBytes(i)
|
||||
charpos := 0
|
||||
|
||||
if i == start.Y {
|
||||
nchars := utf8.RuneCount(l)
|
||||
start.X = util.Clamp(start.X, 0, nchars-1)
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
}
|
||||
|
||||
match := r.FindIndex(l)
|
||||
|
||||
if match != nil {
|
||||
start := Loc{charpos + util.RunePos(l, match[0]), i}
|
||||
end := Loc{charpos + util.RunePos(l, match[1]), i}
|
||||
return [2]Loc{start, end}, true
|
||||
}
|
||||
}
|
||||
return [2]Loc{}, false
|
||||
}
|
||||
|
||||
func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
||||
start.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)
|
||||
|
||||
for i := start.Y; i >= end.Y; i-- {
|
||||
l := b.LineBytes(i)
|
||||
|
||||
if i == start.Y {
|
||||
nchars := utf8.RuneCount(l)
|
||||
start.X = util.Clamp(start.X, 0, nchars-1)
|
||||
l = util.SliceStart(l, start.X)
|
||||
}
|
||||
|
||||
match := r.FindIndex(l)
|
||||
|
||||
if match != nil {
|
||||
start := Loc{util.RunePos(l, match[0]), i}
|
||||
end := Loc{util.RunePos(l, match[1]), i}
|
||||
return [2]Loc{start, end}, true
|
||||
}
|
||||
}
|
||||
return [2]Loc{}, false
|
||||
}
|
||||
|
||||
// FindNext finds the next occurrence of a given string in the buffer
|
||||
// It returns the start and end location of the match (if found) and
|
||||
// a boolean indicating if it was found
|
||||
// May also return an error if the search regex is invalid
|
||||
func (b *Buffer) FindNext(s string, from Loc, down bool) ([2]Loc, bool, error) {
|
||||
if s == "" {
|
||||
return [2]Loc{}, false, nil
|
||||
}
|
||||
|
||||
var r *regexp.Regexp
|
||||
var err error
|
||||
if b.Settings["ignorecase"].(bool) {
|
||||
r, err = regexp.Compile("(?i)" + s)
|
||||
} else {
|
||||
r, err = regexp.Compile(s)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return [2]Loc{}, false, err
|
||||
}
|
||||
|
||||
found := false
|
||||
var l [2]Loc
|
||||
if down {
|
||||
l, found = b.findDown(r, from, b.End())
|
||||
if !found {
|
||||
l, found = b.findDown(r, b.Start(), from)
|
||||
}
|
||||
} else {
|
||||
l, found = b.findUp(r, from, b.Start())
|
||||
if !found {
|
||||
l, found = b.findUp(r, b.End(), from)
|
||||
}
|
||||
}
|
||||
return l, found, nil
|
||||
}
|
||||
@@ -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) {
|
||||
if !main {
|
||||
if main {
|
||||
screen.Screen.ShowCursor(x, y)
|
||||
} else {
|
||||
r, _, _, _ := screen.Screen.GetContent(x, y)
|
||||
@@ -421,8 +421,8 @@ func (w *BufWindow) displayBuffer() {
|
||||
|
||||
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)
|
||||
if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {
|
||||
w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -495,8 +495,8 @@ func (w *BufWindow) displayBuffer() {
|
||||
}
|
||||
|
||||
for _, c := range cursors {
|
||||
if c.X == bloc.X && c.Y == bloc.Y {
|
||||
w.showCursor(w.X+vloc.X, w.Y+vloc.Y, true)
|
||||
if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {
|
||||
w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,10 @@ type InfoBuf struct {
|
||||
GutterMessage bool
|
||||
|
||||
PromptCallback func(resp string, canceled bool)
|
||||
EventCallback func(resp string)
|
||||
}
|
||||
|
||||
// NewBuffer returns a new infobuffer
|
||||
func NewBuffer() *InfoBuf {
|
||||
ib := new(InfoBuf)
|
||||
ib.History = make(map[string][]string)
|
||||
@@ -62,7 +64,11 @@ func (i *InfoBuf) Error(msg ...interface{}) {
|
||||
// TODO: add to log?
|
||||
}
|
||||
|
||||
func (i *InfoBuf) Prompt(prompt string, msg string, callback func(string, bool)) {
|
||||
// Prompt starts a prompt for the user, it takes a prompt, a possibly partially filled in msg
|
||||
// and callbacks executed when the user executes an event and when the user finishes the prompt
|
||||
// The eventcb passes the current user response as the argument and donecb passes the user's message
|
||||
// and a boolean indicating if the prompt was canceled
|
||||
func (i *InfoBuf) Prompt(prompt string, msg string, eventcb func(string), donecb func(string, bool)) {
|
||||
// If we get another prompt mid-prompt we cancel the one getting overwritten
|
||||
if i.HasPrompt {
|
||||
i.DonePrompt(true)
|
||||
@@ -71,21 +77,27 @@ func (i *InfoBuf) Prompt(prompt string, msg string, callback func(string, bool))
|
||||
i.Msg = prompt
|
||||
i.HasPrompt = true
|
||||
i.HasMessage, i.HasError = false, false
|
||||
i.PromptCallback = callback
|
||||
i.PromptCallback = donecb
|
||||
i.EventCallback = eventcb
|
||||
i.Buffer.Insert(i.Buffer.Start(), msg)
|
||||
}
|
||||
|
||||
// DonePrompt finishes the current prompt and indicates whether or not it was canceled
|
||||
func (i *InfoBuf) DonePrompt(canceled bool) {
|
||||
i.HasPrompt = false
|
||||
if canceled {
|
||||
i.PromptCallback("", true)
|
||||
} else {
|
||||
i.PromptCallback(strings.TrimSpace(string(i.LineBytes(0))), false)
|
||||
if i.PromptCallback != nil {
|
||||
if canceled {
|
||||
i.PromptCallback("", true)
|
||||
} else {
|
||||
i.PromptCallback(strings.TrimSpace(string(i.LineBytes(0))), false)
|
||||
}
|
||||
}
|
||||
i.PromptCallback = nil
|
||||
i.EventCallback = nil
|
||||
i.Replace(i.Start(), i.End(), "")
|
||||
}
|
||||
|
||||
// Reset resets the messenger's cursor, message and response
|
||||
// Reset resets the infobuffer's msg and info
|
||||
func (i *InfoBuf) Reset() {
|
||||
i.Msg = ""
|
||||
i.HasPrompt, i.HasMessage, i.HasError = false, false, false
|
||||
|
||||
@@ -188,6 +188,12 @@ func IsStrWhitespace(str string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// RunePos returns the rune index of a given byte index
|
||||
// Make sure the byte index is not between code points
|
||||
func RunePos(b []byte, i int) int {
|
||||
return utf8.RuneCount(b[:i])
|
||||
}
|
||||
|
||||
// TODO: consider changing because of snap segfault
|
||||
// ReplaceHome takes a path as input and replaces ~ at the start of the path with the user's
|
||||
// home directory. Does nothing if the path does not start with '~'.
|
||||
@@ -302,3 +308,12 @@ func GetCharPosInLine(b []byte, visualPos int, tabsize int) int {
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
func Clamp(val, min, max int) int {
|
||||
if val < min {
|
||||
val = min
|
||||
} else if val > max {
|
||||
val = max
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user