update method
Updates the component state in response to a message.
Returns the updated component (often this) and an optional command.
Implementation
@override
(TextInputModel, Cmd?) update(Msg msg) {
final oldPos = _pos;
final cmds = <Cmd>[];
if (msg is MouseMsg) {
if (msg.y != 0) {
if (msg.action == MouseAction.press && msg.button == MouseButton.left) {
_selectionStart = null;
_selectionEnd = null;
_focused = false;
}
return (this, null);
}
final promptWidth = stringWidth(prompt);
final localX = msg.x - promptWidth;
final visibleValue = _value.sublist(_offset, _offsetRight);
final idxInVisible = layout.localCellXToGraphemeIndex(
visibleValue.join(),
localX,
);
final x = _offset + idxInVisible;
if (msg.action == MouseAction.press && msg.button == MouseButton.left) {
_focused = true;
final now = DateTime.now();
if (_lastClickTime != null &&
now.difference(_lastClickTime!) <
const Duration(milliseconds: 500) &&
_lastClickPos == x) {
// Double click: select word
final (start, end) = _findWordAt(x);
_selectionStart = start;
_selectionEnd = end;
_pos = end;
_lastClickTime = now;
_lastClickPos = x;
} else {
position = x;
_selectionStart = _pos;
_selectionEnd = _pos;
_lastClickTime = now;
_lastClickPos = x;
}
} else if (msg.action == MouseAction.motion &&
msg.button == MouseButton.left) {
position = x;
_selectionEnd = _pos;
} else if (msg.action == MouseAction.release) {
if (_selectionStart == _selectionEnd) {
_selectionStart = null;
_selectionEnd = null;
}
}
return (this, null);
}
if (!_focused) {
return (this, null);
}
// Check for suggestion acceptance first
if (msg is KeyMsg && keyMatches(msg.key, [keyMap.acceptSuggestion])) {
if (_canAcceptSuggestion()) {
final suggestion = _matchedSuggestions[_currentSuggestionIndex];
_value = [..._value, ...suggestion.sublist(_value.length)];
cursorEnd();
}
}
if (msg is KeyMsg) {
if (keyMatches(msg.key, [keyMap.copy])) {
final selected = getSelectedText();
if (selected.isNotEmpty) {
return (this, Cmd.setClipboard(selected));
}
}
if (msg.key.type == KeyType.space) {
_insertRunes([0x20]);
return (this, null);
}
if (keyMatches(msg.key, [keyMap.deleteWordBackward])) {
_deleteWordBackward();
} else if (keyMatches(msg.key, [keyMap.deleteCharacterBackward])) {
error = null;
if (_value.isNotEmpty && _pos > 0) {
_value = [
..._value.sublist(0, math.max(0, _pos - 1)),
..._value.sublist(_pos),
];
error = _validate(_value);
if (_pos > 0) position = _pos - 1;
}
} else if (keyMatches(msg.key, [keyMap.wordBackward])) {
_wordBackward();
} else if (keyMatches(msg.key, [keyMap.characterBackward])) {
if (_pos > 0) position = _pos - 1;
} else if (keyMatches(msg.key, [keyMap.wordForward])) {
_wordForward();
} else if (keyMatches(msg.key, [keyMap.characterForward])) {
if (_pos < _value.length) position = _pos + 1;
} else if (keyMatches(msg.key, [keyMap.lineStart])) {
cursorStart();
} else if (keyMatches(msg.key, [keyMap.deleteCharacterForward])) {
if (_value.isNotEmpty && _pos < _value.length) {
_value = [..._value.sublist(0, _pos), ..._value.sublist(_pos + 1)];
error = _validate(_value);
}
} else if (keyMatches(msg.key, [keyMap.lineEnd])) {
cursorEnd();
} else if (keyMatches(msg.key, [keyMap.deleteAfterCursor])) {
_deleteAfterCursor();
} else if (keyMatches(msg.key, [keyMap.deleteBeforeCursor])) {
_deleteBeforeCursor();
} else if (keyMatches(msg.key, [keyMap.paste])) {
// Return paste command - caller handles clipboard
return (this, _pasteCmd());
} else if (keyMatches(msg.key, [keyMap.deleteWordForward])) {
_deleteWordForward();
} else if (keyMatches(msg.key, [keyMap.nextSuggestion])) {
_nextSuggestion();
} else if (keyMatches(msg.key, [keyMap.prevSuggestion])) {
_previousSuggestion();
} else if (msg.key.runes.isNotEmpty) {
// Regular character input
_insertRunes(msg.key.runes);
}
_updateSuggestions();
} else if (msg is PasteMsg) {
_insertRunes(uni.codePoints(msg.content));
} else if (msg is PasteErrorMsg) {
error = msg.error.toString();
}
// Update cursor
final (newCursor, cursorCmd) = cursor.update(msg);
cursor = newCursor;
if (cursorCmd != null) cmds.add(cursorCmd);
// Reset blink if position changed - use focus() to restart blink
if (oldPos != _pos && cursor.mode == CursorMode.blink) {
final (refocusedCursor, blinkCmd) = cursor.focus();
cursor = refocusedCursor;
if (blinkCmd != null) cmds.add(blinkCmd);
}
_handleOverflow();
return (this, cmds.isNotEmpty ? Cmd.batch(cmds) : null);
}