Posts tagged with "textual"

Evolve Words

2 min read

This follows on from my previous post. If you've not read that, it's worth having a dive in first for the background.

The Ruby code I mention, that was written back in 2008, was actually a pair of scripts. The first one, called selection, did what visual-selection does, only visual-selection does it with a nice TUI interface: it takes a random collection of letters and symbols and evolves them into a target phrase.

As covered before: I don't remember all of the details of the conversation that was going on at the time, but I do seem to remember something along the lines of "yes, but you start out and end up with something the same length" and "nothing more complex is made" (let's gloss over the whole "complex" thing for now... well okay let's just gloss over it, end of story; this is just a fun coding exercise).

What I do remember is that the seed of an idea was planted. Fine: how about I start off with one small word, and using a list of English words as the "fitness landscape" that the mutations had to survive in, mutate a population over and over and see what happens. Would I "randomly" create known words, with fewer letters, with the same letters, with more letters?

So this version of the code randomly did three forms of mutation: it would randomly flip a letter, or randomly delete a letter, or randomly insert a random letter. It would do this over and over and eliminate words that aren't in the original list (the simple form of selecting for survival within the landscape).

Like I said last time: never going to convince anyone of anything, but fun to write some code.

This version became selection2.

So, having turned selection into a TUI application with Textual, I had to do the same with this code...

Evolve Words

As before, because it's fun to do so, this leans heavily on the worker API and textual-plotext.

If you want to check out the app itself there's a GitHub repo and it can also be installed from PyPi using pipx.

Visual Selection

4 min read

Over the last few weeks I've had a couple of sessions of working on a library to wrap Plotext -- a popular terminal-based plotting library for Python -- so that it can easily be used in Textual apps; textual-plotext is the result.

I feel it's come together pretty well

But... I've been itching to find a reason to use it in a project of my own.

Meanwhile...

Back in the mid-2000s, when phpBB systems were still the fashion, I used to hang out on a site that was chiefly aimed at the atheist and secular humanist crowd. We'd get a good number of drive-by YEC types who'd want to argue (sorry... debate) and often talk nonsense about biology and the like.

Now, I'm no biologist, I'm no scientist, I'm just a hacker who likes to write code for fun and profit; so any time there was a chance to write some code to help illustrate an idea I'd jump at the chance. I forget the detail now -- this was back in 2008; 15 years ago as of the time of writing -- but one time I remember a conversation was taking place where someone was just flat out claiming that "random mutation" can only cause "loss of information" and could never lead to a "desired result", or some such thing.

If you've ever had, read or watched those debates, you'll know the sort of thing I mean.

So that got me thinking back then, could I write something that could give a simple illustration of how this doesn't quite make sense?

So I had a little hacking session and came up with some Ruby code1 that did what I felt was the job. You'd give it a phrase you wanted it to generate (a stand-in for the current "fitness landscape", in effect), it would then generate a totally random string of that length, and then would set about mutating it, finding mutations that were "fitter" than others (a stand in for selection), breed the best two so far (randomly copy one chunk from another to create a child), then repeat over and over.

When I first wrote it I wasn't sure what to expect; would it ever finish given a reasonably large target string?

It did.

It was fun to code.

It got posted to the BB and of course wasn't in any way persuasive to them (honestly I never expected it would be). I seem to recall it being hand-waved away with calls of there obviously being an intelligent designer involved2.

Anyway, the "meanwhile..." in this: a few times this year I've thought it could be fun to rework this in Python (it's really not that complex after all; just a string-chopping loop really) and use Textual to put a fun UI on it.

So, that's what I did, complete with textual-plotext plot:

Visual Selection in action

While, 15 years on, this isn't going to convince anyone of the underlying point, I think it does serve a good educational purpose. It shows that you can create a fun UI for the terminal, with Textual, with not a lot of code. It also shows off how you can easily create dynamic plots. Plus -- and I think this might be the really important one -- it shows you can write "traditional" tight-loop code in a Textual application and still have a responsive UI; all thanks to the worker API.

The heart of the code for this application is this:

environment = Environment("This is the target string we want to create!")
while not environment.best_fit_found:
    environment.shit_happens()

Sure, there's some detail in the Environment class, but you get the idea: while we've not hit the target, let life find a way. A loop like that would totally bog down an application with a UI without some other work taking place. With Textual and workers the resulting method in the application, complete with code to send updates to the UI, really doesn't look much different:

@work(thread=True, exclusive=True)
def run_world(self, target: str) -> None:
    worker = get_current_worker()
    environment = Environment(target)
    iterations = 0
    self.post_message(self.WorldUpdate(environment, iterations, *environment.best))
    while not worker.is_cancelled and not environment.best_fit_found:
        environment.shit_happens()
        iterations += 1
        if (iterations % 1000) == 0 or environment.best_fit_found:
            self.post_message(
                self.WorldUpdate(environment, iterations, *environment.best)
            )
    if environment.best_fit_found:
        self.post_message(self.Finished(iterations))

I honestly think the worker API is one of the coolest things added to Textual and I so often see people have real "woah!" moments when they get to grips with it.

Anyway... I've covered science, religion, and how Ruby is better than Python, so I'm sure I've annoyed almost everyone. Job done I guess. ;-)

If you want to check out the app itself there's a GitHub repo and it can also be installed from PyPi using pipx.

Expect it to be my tinker project of choice for a wee while; there's a couple of other things I'd like to add to it.


  1. Possibly unpopular opinion with some folk who will read this, but I've long been a fan of Ruby as a language and actually generally prefer it to Python. 

  2. Me, the coder. While utterly missing the point of a simple illustration, while apparently not understanding the concept of an analogy, I guess at least they felt I was intelligent? 

astare v0.8.0 released

1 min read

textual-astare is another Textual-based Python project that I've developed in the last year and I don't believe I've mentioned on this blog. Simply put, it's a took for viewing the abstract syntax tree of Python code, in the terminal.

astare in action

I've just made a small update to it this evening after someone asked for a sensible change I've been meaning to do for a while. When I first read the request I was going to look at it next week, when I have some time off work, but you know how it is when you sit at your desk and have a "quick look".

So anyway, yeah, v0.8.0 is out there and can be installed, with the main changes being:

  • Updated textual-fspicker
  • Updated textual
  • Made it so you can open a directory to browser from the command line.
  • Made opening the current working directory the default.
  • Tweaked the way dark/light mode get toggled so that it's now command-palette-friendly.

I think the code does need a wee bit of tidying -- this was one of my earliest apps built with Textual and my approach to writing Textual apps has changed a fair bit this year, and Textual itself has grown and improved in that time -- but it's still working well for now.

Mandelbrot Commands

2 min read

I don't think I've mentioned it before on this blog, but some time back I decided it would be fun to use Textual to write a Mandelbrot explorer (simple Mandelbrot explorers have been another one of my favourite known problem to try an unknown thing problems). Doing it in the terminal seemed like a fun little hack. I started off with creating textual-canvas and then built textual-mandelbrot on top of that.

Not too long back I added a "command palette" to Textual (I'd prefer to call it a minibuffer, but I get that that's not fashionable these days), but so far I've not used it in any of my own projects; earlier today I thought it could be fun to add it to textual-mandelbrot.

Mandelbrot commands in action

Most of the commands I've added are trivial and really better covered by (and are covered by) keystrokes, but it was a good test and a way to show off how to create a command provider.

Having started this I can see some more useful things to add: for example it might be interesting to add a facility where you can bookmark a specific location, zoom level, iteration value, etc, and revisit later. The command palette would feel like a great way to pull back those bookmarks.

What I really liked though was how easy this was to do. The code to make the commands available is pretty trivial and, I believe, easy to follow. Although I do say so myself I think I managed to design a very accessible API for this.

There's more I'd like to add to that (the Textual command palette itself, I mean), of course; this was just the start. Support for commands that accept and prompt for arguments would be a neat and obvious enhancement (especially if done in a way that's reminiscent of how commands could be defined in CLIM -- I remember really liking how you could create self-documenting and self-completing commands in that).

All in good time...

Textual Query Sandbox Update

2 min read

Since quickly hacking together textual-query-sandbox a few days back, I've made a bunch of small changes here and there. While most have been cosmetic and playing with some ideas, some have also been internal improvements that should make the tool work better.

The most prominent change is one I pondered in the previous post, where I thought it might be interesting to have a small collection of playgrounds grounded together with a TabbedContent. So as of now the tool still has the original playground which had an emphasis on nested containers:

Playground 1

There's now a playground with an emphasis on selecting widgets within containers1:

Playground 2

There's also now a playground that has an emphasis on pulling out widgets based on ID and classes:

Playground 3

The other change you will notice from the original post is the DOM tree shown in the bottom right corner. Note that that isn't there to show your query result (that's the bottom left panel), it's there to help picture how the DOM in the current playground hangs together, and will hopefully help in picturing the structure for when you write a query.

I sense there's still a lot of fun things I could add to this, and I'm still keen on the idea of having the playgrounds "soft coded" in some way, so people can make their own and load them up.

Another thing I want to try and work on is making the display as useful as possible. While I think it's actually pretty neat and clear, there's not a lot of space2 available to show the playground and the results. Finding a good balance is an interesting problem.

For a number of reasons this is turning into a really enjoyable tinker project.


  1. This is, of course, slightly nonsensical wording. Containers are widgets in Textual. Pretty much everything you see in your terminal is a widget, even a Screen is a widget. 

  2. A lot of this of course hinges on how big someone's terminal is. I tend to run a fairly high resolutions with the smallest font I find readable so my terminal windows are often pretty "big"; other people tend to have something much smaller in terms of cell with/height. 

Textual Query Sandbox

3 min read

Sometimes I can have an idea for a Textual widget, library or application on my ideas list for weeks, months even, before I get around to it -- mostly just due to not having the clear time to make a run at getting it going -- and then other times an idea can pop into my head and it has to be created there and then. Has to be!

This happened yesterday evening.

While the tool I built is something I'd thought of before (back around November last year I think) it hadn't even made it to my "list of stuff I should make" that I keep in Apple Reminders; not sure why really. But then yesterday evening a question cropped up on the Textual Discord server that related to the subject and I was reminded of it.

The subject being: Textual DOM queries. I like to think that DOM queries in Textual are pretty easy to do, and well-explained in the docs, but it's fair to admit that they need a bit of practice first, just like any powerful tool. So I was reminded that I'd wanted to write a sandbox application, that would have a practice DOM inside it, an input field to type in a query, and a way of displaying the results.

So textual-query-sandbox was born!

Textual Query Sandbox

In this very first version (which was really quickly put together -- it was something like 15 minutes to write the main code and then probably 45 minutes tweaking styles, adding all the admin stuff to allow deployment to PyPi and writing the README) there's an Input, a display of a group of nested containers with different IDs and classes, and then a Pretty widget at the bottom to show the query result.

If you think this looks like it might be useful to you, it can be installed using either pip or (ideally) pipx:

$ pipx install textual-query-sandbox

and then you can run it with:

$ tqs

At which point load up the Textual query docs, type queries into the input field, hit enter and see what gets highlighted and which widgets end up in the result set at the bottom of the screen.

Like I say: this was a quick hack yesterday evening, I think there's a lot more can go into this. For one thing I think a more interesting practice DOM would be a good idea, with a good mix of widgets; another thing could be having a collection of different DOM playgrounds that can be switched between (a TabbedContent of different playgrounds could be fun here); this could even be taken further such that the user can create their own playground DOM to practice against.

Eventually it would be neat if this could be turned into a library that can be included in a Textual application, as a development-time debug tool, so that on-the-fly test queries can be made.

For now though, it's started, it's under way, and I think the current version probably covers 90% of the use cases for something like this; making for a really quick and easy tool to double-check how to query something.

Unbored v0.6.0

2 min read

Late on last year I wrote about a bunch of new things that I'd added to PyPi, things mostly kicked off by an early dog-fooding session we had at textual HQ. Since then I've been slowly doing my best to keep the applications up to date with Textual.

Unbored

As much as possible we try and not make breaking changes with the framework, but at the same time it is still 0.x software and there's still new ways of doing things being designed so there's going to be the odd break in approach now and again.

Unbored, my kind of silly self-populating TODO list application, has been sitting atop Textual 0.20.x for a while now and earlier today I checked how it was getting in with 0.32.0 and... actually surprisingly okay. Not perfect, there were a couple of things that had suffered from bitrot, but it wasn't crashing.

The main thing I needed to change was the ability to focus a couple of containers (they didn't used to receive focus by default, now they do so I had to tell them not to again), and that was about it.

While I was in there I also updated the application so that I dropped the nifty little slide-in error dialog I'd made, and instead embraced the new Textual notification system.

While the application itself is a bit silly, and likely of no real use to anyone, I feel it's a pretty good barometer application, helping me check what the experience is like when it comes to maintaining a Textual application and the needs to keep on top of changes to Textual.

It goes without saying, I hope, that really you should pin the Textual dependency for your applications, and upgrade in a controlled and tested way; for this though it's less crucial and is a good test of the state of the ecosystem, and on the remote chance that anyone is using it, it'll be helpful to me if it does break and they yell.

textual-canvas v0.2.0

1 min read

Demo of textual-canvas

Given that for a good chunk of this year I've been a bit lax about writing here, there's a couple or so coding projects I've not written about (well, not on here anyway -- I have spoken lots about them over on Fosstodon). One such project is textual-canvas.

As the name might suggest, it's a "canvas" for Textual applications, which provides a pretty basic interface for drawing pixels, lines and circles -- and of course any other shape you are able to build up from those basics.

I've just released a quick update after it was requested that I add a clear method to the Canvas widget; a request that makes perfect sense.

A new GitHub profile README

2 min read

My new GitHub banner

Ever since GitHub introduced the profile README1 I've had a massively low-effort one in place. I made the repo, quickly wrote the file, and then sort of forgot about it. Well, I didn't so much forget as just keep looking at it and thinking "I should do something better with that one day".

Thing is, while there are lots of fancy approaches out there, and lots of neat generator tools and the like... they just weren't for me.

Then yesterday, over my second morning coffee, after getting my blog environment up and going again, I had an idea. It could be cool to use Textual's screenshot facility to make something terminal-themed! I mean, while it's not all I am these days, so much of what I'm doing right now is aimed at the terminal.

So... what to do? Then I thought it could be cool to knock up some sort of login screen type thing; with a banner. One visit to an online large terminal text generator site later, I had some banner text. All that was left was to write a simple Textual application to create the "screen".

The main layout is simple enough:

def compose(self) -> ComposeResult:
    yield Label(NAME, classes="banner")
    yield Label(PRATTLE)
    yield Label("github.com/davep login: [reverse] [/]")

where NAME contains the banner and PRATTLE contains the "login message". With some Textual CSS sprinkled over it to give the exact layout and colour I wanted, all that was left was to make the snapshot. This was easy enough too.

While the whole thing isn't fully documented just yet, Textual does have a great tool for automatically running an application and interacting with it; that meant I could easily write a function to load up my app and save the screenshot:

async def make_banner() -> None:
    async with GitHubBannerApp().run_test() as pilot:
        pilot.app.save_screenshot("davep.svg")

Of course, that needs running async, but that's simple enough:

if __name__ == "__main__":
    asyncio.run(make_banner())

Throw in a Makefile so I don't forget what I'm supposed to run:

.PHONY: all
all:
    pipenv run python make_banner.py

and that's it! Job done!

From here onward I guess I could have some real fun with this. It would be simple enough I guess to modify the code so that it changes what's displayed over time; perhaps show a "last login" value that relates to recently activity or something; any number of things; and then run it in a cron job and update the repository.

For now though... I'll stick with keeping things nice and simple.


  1. It was actually kind of annoying when they introduced it because the repo it uses is named after your user name. I already had a davep repo: it was a private repo where I was slowly working on a (now abandoned, I'll start it again some day I'm sure) ground-up rewrite of my davep.org website. 

OIDIA

2 min read

Another little thing that's up on PyPi now, which is the final bit of fallout from the Textual dogfooding sessions, is a little project I'm calling OIDIA.

The application is a streak tracker. I'm quite the fan of streak trackers. I've used a few over the years, both to help keep me motivated and honest, and also to help me track that I've avoided unhelpful things too. Now, most of the apps I've used, and use now, tend to have reminders and counts and stats and are all about "DO NOT BREAK THE STREAK OR ELSE" and that's mostly fine, but...

To keep things simple and to purely concentrate on how to build Textual apps, I made this a "non-judgement" streak tracker. It's designed to be really simple: you add a streak, you bump up/down the number of times you did (or didn't do) the thing related to that streak, for each day, and that's it.

No totals. No stats. No reminders and bugging. No judgement.

Here it is in action:

When I started it, I wasn't quite sure how I wanted to store the data. Throwing it in a SQLite database held some appeal, but that also felt like a lot of faff for something so simple. Also, I wanted to make the data as easy to get at, to use elsewhere, and to hack on, as possible. So in the end I went with a simple JSON file.

On macOS and GNU/Linux streaks.json lives in ~/.local/share/oidia, on Windows it'll be in... I'm not sure off the top of my head actually; it'll be in whatever directory the handy xdg library has chosen. and because it's JSON that means that something like this:

OIDIA in action

ends up looking like this:

[
    {
        "title": "Hack some Python",
        "days": {
            "2022-12-02": 1,
            "2022-12-03": 1,
            "2022-12-04": 1,
            "2022-12-05": 1,
            "2022-12-06": 1,
            "2022-12-07": 1,
            "2022-12-08": 1,
            "2022-12-01": 1,
            "2022-11-30": 1,
            "2022-11-29": 1,
            "2022-11-28": 1
        }
    },
    {
        "title": "Brush my teeth",
        "days": {
            "2022-12-02": 2,
            "2022-12-03": 2,
            "2022-12-04": 2,
            "2022-12-05": 2,
            "2022-12-06": 2,
            "2022-12-07": 2,
            "2022-12-08": 1,
            "2022-12-01": 2,
            "2022-11-30": 2,
            "2022-11-29": 2,
            "2022-11-28": 2
        }
    },
    {
        "title": "Walk",
        "days": {
            "2022-12-02": 1,
            "2022-12-03": 1,
            "2022-12-04": 1,
            "2022-12-05": 1,
            "2022-12-06": 1,
            "2022-12-07": 1,
            "2022-12-08": 1,
            "2022-12-01": 1,
            "2022-11-30": 1,
            "2022-11-29": 1,
            "2022-11-28": 1
        }
    },
    {
        "title": "Run 5k",
        "days": {
            "2022-12-03": 2,
            "2022-12-05": 1,
            "2022-11-30": 1,
            "2022-11-28": 2
        }
    },
    {
        "title": "Run 10k",
        "days": {
            "2022-12-03": 1,
            "2022-11-28": 1
        }
    }
]

Of course, it remains to be seen how well that actually scales; possibly not so well over a long period of time, but this was written more as another way to explore Textual than anything else. Even then, it would be pretty trivial to update to something better for holding the data.

If this seems like your thing (and I will be supporting it and onward developing it) you can find it over on PyPi, which means it can be installed with pip or the ever-handy pipx:

$ pipx install oidia