-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
fix(masked input): fix selection #6287
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
base: main
Are you sure you want to change the base?
fix(masked input): fix selection #6287
Conversation
Fix `MaskedInput` not highlighting the selected text. Fixes Textualize#5495
While attempting to fix some bugs in the `MaskedInput`, I managed to break the overwrite typing without any tests failing. Add a test for the overwrite typing to prevent regressions.
Add `_Template.replace` method and move all the code from `insert_text_at_cursor` to this new method. This refactor should help clarify some current bugs in the `MaskedInput` related to inserting/replacing text.
When text selection was added to the `Input` widget in Textualize#5340, it was unfortunately overlooked that `MaskedInput` inherits from this widget. Add an override for the `MaskedInput.replace` method to ensure proper functionality when replacing selecting text. Fixes Textualize#5493
Fix `MaskedInput` method override signatures missing the `select` parameter.
Fix a bad separation of concerns where `MaskedInput` defers to its template to move the cursor.
Fix bindings like `shift+right` that should move the cursor and select not selecting text in `MaskedInput`.
Add tests for replacing selected text in the `MaskedInput`.
When _deleting_ text in a `MaskedInput`, this removes any empty text at the end of the input value. For example, deleting everything to the right in 'ABCDE-FGHIJ-KLMNO-PQRST' results in the value 'A'. Currently _replacing_ text will leave behind this empty text. For example, selecting all text and pressing 'A' instead results in the value 'A - - - '. Fix replacing text in the `MaskedInput` to remove any empty text at the end of the input value.
|
There's still an issue to tackle when the The problem is that the design of this widget never accounted for this. Even before the problems when text selection was added, pressing home then typing would crash with an assertion error. Here's an example
Example codefrom textual.app import App, ComposeResult
from textual.widgets import MaskedInput
class ExampleApp(App):
def compose(self) -> ComposeResult:
yield MaskedInput("(999) 999-9999;_")
if __name__ == "__main__":
app = ExampleApp()
app.run()The problems start because, unlike other masked inputs where the initial value is empty, the value starts with the first separator. Before the user even begins interacting with the input, it is already flagged as invalid and there's this strange selection. Trying to start typing will now crash with the assertion error mentioned above. The reason is that the I'm really not sure what the correct solution is here. I tried checking other examples of masked inputs for comparison, but Textual's widget seems very different where it restricts editing based on the current value length. For example, here's a similar masked input in Qt (which I believe Textual's widget took some inspiration from). This must have some special handling when the mask begins with a separator, but I don't think would work in the
Example codeimport sys
from PySide6.QtWidgets import QApplication, QDialog, QLabel, QLineEdit, QVBoxLayout
class PhoneDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
layout = QVBoxLayout()
phone_label = QLabel("Phone Number")
layout.addWidget(phone_label)
self.phone_input = QLineEdit()
self.phone_input.setInputMask("(999) 999-9999;_")
layout.addWidget(self.phone_input)
self.setLayout(layout)
if __name__ == "__main__":
app = QApplication(sys.argv)
dialog = PhoneDialog()
dialog.show()
sys.exit(app.exec())Any suggestions for resolving this issue would be welcome! |
|
Hi since this appears to be one I found, Edit1: Edit2: Edit 3: Edit 4: Edit 5: Edit 6: |
|
@James-San Thanks for your comment, your input on this issue would be really helpful!
When the template mask begins with a separator, you would expect the cursor position to start at 1? The problem is that this separator is automatically inserted to advance the cursor position (see above). Currently you don't realise the Since the initial separator means the value isn't empty, this also results in the input already flagged as invalid before the user even begins interacting with the widget. This is why I'm struggling to find a solution: when the template mask begins with a separator, what's the input value and the resulting selection/cursor position? |
|
Yes I can see why that might be an issue. I'm playing around with the replace algorithm from your commit to make it work but I think it needs to keep multiple index. Basically you have to scan replacement text char by char and then:
when patternmatching you of course return None as well. Something like that logic. It's kinda lexer-like in a way. Or finite state automaton. |
|
Sorry but I'm not sure I follow, Putting aside the replace algorithm for the moment, how would this resolve an empty value when it starts with a separator? |
|
So the issue is that by default there is selection on focus but field is empty. Thus when you don't clear selection before trying input - it will throw assertion error in this version right? But that's because it tries to replace that first character of string and finds separator where it wouldn't expect it. Now with the idea above, since it tries to check if user didn't enter valid character omitting the separator - it will no longer crash. |









When text selection was added to the
Inputwidget in #5340, it was unfortunately overlooked thatMaskedInputinherits from this widget.MaskedInputdoes not highlight the selected text #5495Please review the following checklist.