You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I might have a very fundamental lack of understanding of how textual Apps are supposed to communicate internally.
My program is meant to help create shell pipelines, inspired by pipecut, which sadly never really materialised. When you type a longer pipeline on the shell and suddenly think that the third program from the left might need a different argument after all, it can be a pain editing on the command line. That's where this comes in.
It has two Widgets above each other, the top one shows the pipeline, the lower one shows the output.
The top one is a class CliDisplay(HorizontalScroll) that contains a ContentSwitcher which can show one of three versions of the pipeline, each represented by a subclass of class Pipeline(Horizontal).
Those subclasses show the pipeline broken down into its individual parts (each argument in its own Input field), or broken down into just individual commands with their respective arguments all in one Input field, or not broken down at all (the entire pipeline in one field like it would be in the shell).
For example the pipeline ls | grep -v py$ is composed like so:
Now, the class DefaultScreen(Screen) that holds the above construct has 2 reactives that represent the raw pipeline as a str, and the parsed pipeline as a list[list[str]]; these reactives are data_binded to the same reactives in the CliDisplay, where they are further bound down to the 3 Pipeline instances.
That way, I have one source of truth for what the edited pipeline looks like that is consistent when the user changes the ContentSwitcher to another view. I might remove the parsed pipeline reactive in the future and only have the raw one as source of truth, but that's not important right now.
On Enter, I want to execute the currently displayed pipeline (and display the result in the CliResult pane, but that is beside the point here and largely works). But to execute it, I have to reparse the pipeline as it is currently represented in the currently active Pipeline in the ContentSwitcher. The reparsing can also be triggered by a key combination without executing it. When that happens, the Pipeline needs to recompose itself, because the user might have edited on the fly in a way that goes against the current view mode. What I mean by that is, e.g. in FullSplit mode, where each argument is supposed to have its own Input field, the user might've just added more arguments without creating new inputs first, because it is faster. So the FullSplitPipeline from the above example might now look like this:
Reparsing takes this, constructs the string representation out of it (ls -la | grep -v py$), parses that, and recreates the content of the FullSplitPipeline by recomposeing, so that Input('ls -la) in effect gets split up into Input('ls') + Input('-la').
So far, so good. The problem is, since the Inputs are being destroyed and recreated, the widget focus is lost, as well as the cursor position. When I reparse the pipeline, or the user switches to another Pipeline in the ContentSwitcher, I save which Input was focused and its cursor position and calculate what position that maps to in the str-representation so I can restore it afterwards.
Anything I tried so far does not work because the timing of when textual executes on_mount, call_after_refresh, sends Messages/executes on_message_name, or whatever else I come up with to make this work just never works out. E.g. when I try to set the focus and cursor position in the Pipelines on_mount, then it turns out that runs before any of the subordinate Inputs are mounted, so it can't find anything.
In short, I seem to have a fundamental misunderstanding of how textual works timing wise, and any pointers and ideas of how you would solve this problem would really be appreciated.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
Hello everyone,
I might have a very fundamental lack of understanding of how textual Apps are supposed to communicate internally.
My program is meant to help create shell pipelines, inspired by pipecut, which sadly never really materialised. When you type a longer pipeline on the shell and suddenly think that the third program from the left might need a different argument after all, it can be a pain editing on the command line. That's where this comes in.
It has two Widgets above each other, the top one shows the pipeline, the lower one shows the output.
The top one is a
class CliDisplay(HorizontalScroll)
that contains aContentSwitcher
which can show one of three versions of the pipeline, each represented by a subclass ofclass Pipeline(Horizontal)
.Those subclasses show the pipeline broken down into its individual parts (each argument in its own
Input
field), or broken down into just individual commands with their respective arguments all in oneInput
field, or not broken down at all (the entire pipeline in one field like it would be in the shell).For example the pipeline
ls | grep -v py$
iscompose
d like so:Now, the
class DefaultScreen(Screen)
that holds the above construct has 2 reactives that represent the raw pipeline as astr
, and the parsed pipeline as alist[list[str]]
; these reactives aredata_bind
ed to the same reactives in theCliDisplay
, where they are further bound down to the 3 Pipeline instances.That way, I have one source of truth for what the edited pipeline looks like that is consistent when the user changes the
ContentSwitcher
to another view. I might remove the parsed pipeline reactive in the future and only have the raw one as source of truth, but that's not important right now.On
Enter
, I want to execute the currently displayed pipeline (and display the result in theCliResult
pane, but that is beside the point here and largely works). But to execute it, I have to reparse the pipeline as it is currently represented in the currently activePipeline
in theContentSwitcher
. The reparsing can also be triggered by a key combination without executing it. When that happens, thePipeline
needs torecompose
itself, because the user might have edited on the fly in a way that goes against the current view mode. What I mean by that is, e.g. in FullSplit mode, where each argument is supposed to have its ownInput
field, the user might've just added more arguments without creating new inputs first, because it is faster. So theFullSplitPipeline
from the above example might now look like this:Reparsing takes this, constructs the string representation out of it (
ls -la | grep -v py$
), parses that, and recreates the content of theFullSplitPipeline
byrecompose
ing, so thatInput('ls -la)
in effect gets split up intoInput('ls')
+Input('-la')
.So far, so good. The problem is, since the
Inputs
are being destroyed and recreated, the widget focus is lost, as well as the cursor position. When I reparse the pipeline, or the user switches to anotherPipeline
in theContentSwitcher
, I save whichInput
was focused and its cursor position and calculate what position that maps to in thestr
-representation so I can restore it afterwards.Anything I tried so far does not work because the timing of when textual executes
on_mount
,call_after_refresh
, sends Messages/executeson_message_name
, or whatever else I come up with to make this work just never works out. E.g. when I try to set the focus and cursor position in thePipeline
son_mount
, then it turns out that runs before any of the subordinateInput
s are mounted, so it can't find anything.In short, I seem to have a fundamental misunderstanding of how textual works timing wise, and any pointers and ideas of how you would solve this problem would really be appreciated.
Beta Was this translation helpful? Give feedback.
All reactions