Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add entry.Insert API to insert text at current cursor position #5334

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 48 additions & 40 deletions widget/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,53 @@ func (e *Entry) Unbind() {
e.binder.Unbind()
}

// InsertAtCursor inserts the given text into the Entry at the current cursor position.
// If text is currently selected, the selected text will be replaced
// with the inserted content.
//
// Since: 2.6
func (e *Entry) InsertAtCursor(text string) {
if text == "" {
changed := e.selecting && e.eraseSelection()

if changed {
e.Refresh()
}

return // Nothing to paste into the text content.
}

if !e.MultiLine {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this code is specific to clipboard so maybe it should be outside of the new insert API?

If you think it does belong here please document it in the API doc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matwachich Do you have an opinion on whether the InsertAtCursor API should also reformat multiline text when inserting into a single-line entry (like paste does)? I feel like it maybe shouldn't since it'd be the developer who controls what is being inserted, vs a user action like paste.

// format clipboard content to be compatible with single line entry
text = strings.Replace(text, "\n", " ", -1)
}

if e.selecting {
e.eraseSelection()
}

runes := []rune(text)
pos := e.cursorTextPos()
provider := e.textProvider()
provider.insertAt(pos, runes)

e.undoStack.Add(&entryModifyAction{
Position: pos,
Text: runes,
})
content := provider.String()
e.updateText(content, false)
e.CursorRow, e.CursorColumn = e.rowColFromTextPos(pos + len(runes))
cb := e.OnChanged

e.validate()
if cb != nil {
cb(content) // We know that the text has changed.
}

e.Refresh() // placing the cursor (and refreshing) happens last}
}

// copyToClipboard copies the current selection to a given clipboard.
// This does nothing if it is a concealed entry.
func (e *Entry) copyToClipboard(clipboard fyne.Clipboard) {
Expand Down Expand Up @@ -1014,46 +1061,7 @@ func (e *Entry) getRowCol(p fyne.Position) (int, int) {
// pasteFromClipboard inserts text from the clipboard content,
// starting from the cursor position.
func (e *Entry) pasteFromClipboard(clipboard fyne.Clipboard) {
text := clipboard.Content()
if text == "" {
changed := e.selecting && e.eraseSelection()

if changed {
e.Refresh()
}

return // Nothing to paste into the text content.
}

if !e.MultiLine {
// format clipboard content to be compatible with single line entry
text = strings.Replace(text, "\n", " ", -1)
}

if e.selecting {
e.eraseSelection()
}

runes := []rune(text)
pos := e.cursorTextPos()
provider := e.textProvider()
provider.insertAt(pos, runes)

e.undoStack.Add(&entryModifyAction{
Position: pos,
Text: runes,
})
content := provider.String()
e.updateText(content, false)
e.CursorRow, e.CursorColumn = e.rowColFromTextPos(pos + len(runes))
cb := e.OnChanged

e.validate()
if cb != nil {
cb(content) // We know that the text has changed.
}

e.Refresh() // placing the cursor (and refreshing) happens last
e.InsertAtCursor(clipboard.Content())
}

// placeholderProvider returns the placeholder text handler for this entry
Expand Down
76 changes: 76 additions & 0 deletions widget/entry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,82 @@ func TestEntry_OnPaste(t *testing.T) {
}
}

func TestEntry_InsertAtCursor(t *testing.T) {
tests := []struct {
name string
entry *widget.Entry
insertText string
wantText string
wantRow, wantCol int
}{
{
name: "singleline: empty content",
entry: widget.NewEntry(),
insertText: "",
wantText: "",
wantRow: 0,
wantCol: 0,
},
{
name: "singleline: simple text",
entry: widget.NewEntry(),
insertText: "clipboard content",
wantText: "clipboard content",
wantRow: 0,
wantCol: 17,
},
{
name: "singleline: UTF8 text",
entry: widget.NewEntry(),
insertText: "Hié™שרה",
wantText: "Hié™שרה",
wantRow: 0,
wantCol: 7,
},
{
name: "singleline: with new line",
entry: widget.NewEntry(),
insertText: "clipboard\ncontent",
wantText: "clipboard content",
wantRow: 0,
wantCol: 17,
},
{
name: "singleline: with tab",
entry: widget.NewEntry(),
insertText: "clipboard\tcontent",
wantText: "clipboard\tcontent",
wantRow: 0,
wantCol: 17,
},
{
name: "password: with new line",
entry: widget.NewPasswordEntry(),
insertText: "3SB=y+)z\nkHGK(hx6 -e_\"1TZu q^bF3^$u H[:e\"1O.",
wantText: `3SB=y+)z kHGK(hx6 -e_"1TZu q^bF3^$u H[:e"1O.`,
wantRow: 0,
wantCol: 44,
},
{
name: "multiline: with new line",
entry: widget.NewMultiLineEntry(),
insertText: "clipboard\ncontent",
wantText: "clipboard\ncontent",
wantRow: 1,
wantCol: 7,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.entry.InsertAtCursor(tt.insertText)
assert.Equal(t, tt.wantText, tt.entry.Text)
assert.Equal(t, tt.wantRow, tt.entry.CursorRow)
assert.Equal(t, tt.wantCol, tt.entry.CursorColumn)
})
}
}

func TestEntry_PageUpDown(t *testing.T) {
t.Run("single line", func(*testing.T) {
e, window := setupImageTest(t, false)
Expand Down
Loading