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

interactive: change to ListView (1/7) #1788

Merged
merged 5 commits into from
Nov 17, 2023
Merged
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
54 changes: 11 additions & 43 deletions tests/interactive/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,38 +95,18 @@ async def test_buttons():
"""
)

# Select a pass
app.passes_selection_list.select(
convert_func_to_riscv_func.ConvertFuncToRiscvFuncPass
)

# assert that pass selection affected Output Text Area
await pilot.pause()
# assert that the Input and Output Text Area's have changed
assert (
app.output_text_area.text
== """builtin.module {
riscv.assembly_section ".text" {
riscv.directive ".globl" "hello"
riscv.directive ".p2align" "2"
riscv_func.func @hello(%n : !riscv.reg<a0>) -> !riscv.reg<a0> {
%0 = riscv.mv %n : (!riscv.reg<a0>) -> !riscv.reg<>
%n_1 = builtin.unrealized_conversion_cast %0 : !riscv.reg<> to index
%two = arith.constant 2 : index
%res = arith.muli %n_1, %two : index
%1 = builtin.unrealized_conversion_cast %res : index to !riscv.reg<>
%2 = riscv.mv %1 : (!riscv.reg<>) -> !riscv.reg<a0>
riscv_func.return %2 : !riscv.reg<a0>
}
}
}
"""
app.input_text_area.text
== """
func.func @hello(%n : index) -> index {
%two = arith.constant 2 : index
%res = arith.muli %n, %two : index
func.return %res : index
}
"""
)
# press "clear passes" button
await pilot.click("#clear_selection_list_button")

# assder that the Output Text Area and current_module have the expected results
await pilot.pause()
assert app.passes_selection_list.selected == []
assert (
app.output_text_area.text
== """builtin.module {
Expand All @@ -138,18 +118,6 @@ async def test_buttons():
}
"""
)
index = IndexType()

expected_module = ModuleOp(Region([Block()]))
with ImplicitBuilder(expected_module.body):
function = func.FuncOp("hello", ((index,), (index,)))
with ImplicitBuilder(function.body) as (n,):
two = arith.Constant(IntegerAttr(2, index)).result
res = arith.Muli(n, two)
func.Return(res)

assert isinstance(app.current_module, ModuleOp)
assert app.current_module.is_structurally_equivalent(expected_module)

# Test clicking the "clear input" button
await pilot.click("#clear_input_button")
Expand Down Expand Up @@ -190,8 +158,8 @@ async def test_passes():
)

# Select a pass
app.passes_selection_list.select(
convert_func_to_riscv_func.ConvertFuncToRiscvFuncPass
app.pass_pipeline = tuple(
(*app.pass_pipeline, convert_func_to_riscv_func.ConvertFuncToRiscvFuncPass)
)

# assert that the Output Text Area has changed accordingly
Expand Down
86 changes: 37 additions & 49 deletions xdsl/interactive/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from textual.app import App, ComposeResult
from textual.containers import Horizontal, ScrollableContainer, Vertical
from textual.reactive import reactive
from textual.widgets import Button, Footer, Label, SelectionList, TextArea
from textual.widgets import Button, Footer, Label, ListItem, ListView, TextArea
from textual.widgets.text_area import TextAreaTheme

from xdsl.dialects.builtin import ModuleOp
Expand All @@ -26,7 +26,7 @@

from ._pasteboard import pyclip_copy

ALL_PASSES = tuple(get_all_passes())
ALL_PASSES = tuple(sorted((p.name, p) for p in get_all_passes()))
"""Contains the list of xDSL passes."""


Expand Down Expand Up @@ -64,39 +64,35 @@ class InputApp(App[None]):
Reactive variable used to save the current state of the modified Input TextArea
(i.e. is the Output TextArea).
"""
pass_pipeline = reactive(tuple[type[ModulePass], ...])
"""Reactive variable that saves the list of selected passes."""

input_text_area: TextArea
"""Input TextArea."""
output_text_area: OutputTextArea
"""Output TextArea."""
passes_selection_list: SelectionList[type[ModulePass]]
"""
SelectionList displaying the passes available to apply. Passes can be selected or
unselected (i.e. applied or removed).
"""

selected_query_label: Label
"""Display selected passes."""
passes_list_view: ListView
"""ListView displaying the passes available to apply."""

def __init__(self):
self.input_text_area = TextArea(id="input")
self.output_text_area = OutputTextArea(id="output")
self.passes_selection_list = SelectionList(id="passes_selection_list")
self.passes_list_view = ListView(id="passes_list_view")
self.selected_query_label = Label("", id="selected_passes_label")

super().__init__()

def compose(self) -> ComposeResult:
"""
Creates the required widgets, events, etc.
Get the list of xDSL passes, add them to an array in "Selection" format (so it
can be added to a Selection List) and sort the list in alphabetical order.
"""

with Horizontal(id="top_container"):
yield self.passes_selection_list
yield self.passes_list_view
with Horizontal(id="button_and_selected_horziontal"):
with Vertical(id="buttons"):
yield Button("Clear Passes", id="clear_selection_list_button")
yield Button("Copy Query", id="copy_query_button")
with ScrollableContainer(id="selected_passes"):
yield self.selected_query_label
Expand All @@ -109,45 +105,52 @@ def compose(self) -> ComposeResult:
yield Button("Copy Output", id="copy_output_button")
yield Footer()

@on(SelectionList.SelectedChanged)
def update_selected_view(self) -> None:
@on(ListView.Selected)
def update_pass_pipeline(self, event: ListView.Selected) -> None:
"""
When a new selection is made, the reactive variable storing the list of selected
passes is updated.
"""
When the SelectionList selection changes (i.e. a pass was selected or
deselected), update the label to show the respective generated query in the Label.
selected_pass = event.item.name
for name, value in ALL_PASSES:
if name == selected_pass:
self.pass_pipeline = tuple((*self.pass_pipeline, value))
return

def watch_pass_pipeline(self) -> None:
"""
new_passes = "\n" + (", " + "\n").join(
p.name for p in self.passes_selection_list.selected
)
When the reactive variable pass_pipeline changes, this function
is called and updates the label to show the respective generated query in the Label.
"""
new_passes = "\n" + (", " + "\n").join(p.name for p in self.pass_pipeline)
new_label = f"xdsl-opt -p {new_passes}"
self.query_one(Label).update(new_label)
self.selected_query_label.update(new_label)
self.update_current_module()

@on(SelectionList.SelectedChanged)
@on(TextArea.Changed, "#input")
def update_current_module(self) -> None:
"""
Function called when the Input TextArea is changed or a pass is selected/
unselected. This function parses the Input IR, applies the selected pass(es) and
updates the Output TextArea.
Function to parse the input and to apply the list of selected passes to it.
"""
input_text = self.input_text_area.text
selected_passes = self.passes_selection_list.selected

if (input_text) == "":
self.current_module = None
return
try:
ctx = MLContext(True)
for dialect in get_all_dialects():
ctx.load_dialect(dialect)
parser = Parser(ctx, input_text)
module = parser.parse_module()
pipeline = PipelinePass([p() for p in selected_passes])
pipeline = PipelinePass([p() for p in self.pass_pipeline])
pipeline.apply(ctx, module)
self.current_module = module
except Exception as e:
self.current_module = e

def watch_current_module(self):
"""
Function called when the current_module reactive variable is updated. This
function updates the Output TextArea.
Function to update the Output TextArea.
"""
match self.current_module:
case None:
Expand Down Expand Up @@ -175,17 +178,12 @@ def on_mount(self) -> None:
self.query_one("#input_container").border_title = "Input xDSL IR"
self.query_one("#output_container").border_title = "Output xDSL IR"
self.query_one(
"#passes_selection_list"
"#passes_list_view"
).border_title = "Choose a pass or multiple passes to be applied."
self.query_one("#selected_passes").border_title = "Selected passes/query"
# aids in the construction of the seleciton list containing all the passes
selections = sorted((value.name, value) for value in ALL_PASSES)

# type error due to Textual Bug requires pyright ignore
# Link to issue: https://github.com/xdslproject/xdsl/issues/1777
self.passes_selection_list.add_options( # pyright: ignore[reportUnknownMemberType]
selections
)
for n, _ in ALL_PASSES:
self.passes_list_view.append(ListItem(Label(n), name=n))
superlopuh marked this conversation as resolved.
Show resolved Hide resolved

def action_toggle_dark(self) -> None:
"""An action to toggle dark mode."""
Expand All @@ -205,20 +203,10 @@ def copy_output(self, event: Button.Pressed) -> None:
"""Output TextArea is copied when "Copy Output" button is pressed."""
pyclip_copy(self.output_text_area.text)

@on(Button.Pressed, "#clear_selection_list_button")
def clear_selection_list(self, event: Button.Pressed) -> None:
"""
SelectionList is cleared (i.e. all selected passes are unselected) when
"Clear Passes" button is pressed.
"""
self.passes_selection_list.deselect_all()

@on(Button.Pressed, "#copy_query_button")
def copy_query(self, event: Button.Pressed) -> None:
"""Selected passes/query Label is copied when "Copy Query" button is pressed."""
selected_passes = "\n" + (", " + "\n").join(
p.name for p in self.passes_selection_list.selected
)
selected_passes = "\n" + (", " + "\n").join(p.name for p in self.pass_pipeline)
query = f"xdsl-opt -p {selected_passes}"
pyclip_copy(query)

Expand Down
6 changes: 3 additions & 3 deletions xdsl/interactive/app.tcss
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# SelectionList
#passes_selection_list {
# ListView
#passes_list_view{
border: heavy $accent-darken-1;
border-title-color: $warning-lighten-1;
border-title-style: bold;
border-title-align: center;
height: 100%;
}

# VerticalScroll(Button, Button)
# Vertical(Button)
#buttons{
layout: vertical;
width: auto;
Expand Down