The idea for this one popped into my head while on the bus back from Textual Towers this evening. So after dinner and some nonsense on TV I had to visit my desk and do a quick hack.
This is textual-countdown, a subtle but I think useful countdown widget for Textual applications.
The idea is that you compose it somewhere into your screen, and when you start the countdown the bar highlights and then starts to shrink down to "nothing" in the middle of its display. When the countdown ends a message is posted so you can then perform the task that was being waited for in an event handler.
Not really a novel thing, I've seen this kind of thing before on the web; I'm sure we all have. I just thought it would be a fun idea for Textual applications too.
I envisage using this where, perhaps, an application needs to wait for an API-visiting cooldown period, or perhaps as a subtle countdown for a question in a quiz; something like that.
Anyway, if this sounds like it's something useful for your Textual applications, it's now available from PyPi and, of course, the source is over on GitHub.
This is actually the second release of OSHit since I first announced it a week back, and I'll get to that other release in a moment.
I've just published v0.2.0, which isn't a very substantial release, but which bumps the required version of Textual to v0.47.1 and has some fun with the new nested CSS feature.
Underlying the point of this release was me taking a "real world" application of mine and nesting as much of the CSS within it as possible, in part to get a feel for how and when it's useful, but also to give it a proper test in a "proper" application. In doing so I think I've found one bug.
Dogfooding is always a good idea.
The main visible change in this release is I've played around with the look of the comments dialog a bit:
I'm still narrowing this down, but I think I prefer this look to what I started out with.
Another change I made was also to the comments dialog. Before, if you performed the "expand comments" action on a comment card that already had its comments expanded, it would move focus to the first child comment; this was a deliberate choice that felt right at the time. Having used the app for a few days now I've realised that making it an open/close toggle is far more useful. So that's what I've done.
Some riffed off the opening paragraph, ignoring the tool itself.
Some riffed off the opening paragraph in a self-reflective way.
Some riffed off the opening paragraph in a "I never see the problem" way.
One or two did the usual "why even bother building that when $TOOL_OF_CHOICE exists?" dance to show their terminal purity.
One or two posted genuinely useful links to other similar projects.
The biggest tree of comments was kind of a fight.
One comment caught my eye though; someone reported having a problem running it. My initial thought on reading it was "my dude, seriously, you're going to report the problem in some random comment on HN rather than raise an issue with the author?!?".
All of which is to say: if you run into a problem with some FOSS project, be like @mihaitodor. Issue that thing so the developer gets to know about it; don't assume they'll be reading some random comment section, social media site or Discord server!
That and don't make 500+ HTTP requests at once; that might not end well for some.
I know I'm not alone in having a relationship with the orange site that is... weird. I generally dislike the culture there, it's almost impossible to read any of the comments without being frustrated about the industry I work in or am adjacent to and some of the people who inhabit it; but as a link aggregator of stuff I might find interesting... I honestly can't think of anywhere better. So, yes, I've been a fairly avid reader of HackerNews for many years, and have even had an account there for over 4 years.
Given this, for a wee while now, I've been meaning to knock up a terminal-based client for it using Textual.
So after work on Tinboard settled down I got the urge to start a new pet project (not abandoning Tinboard, I'm still going to be tweaking and extending it of course) and finally knocking up that client seemed like the one.
Orange Site Hit is the result.
It's worth making clear from the very start: this is a read-only reader. There is no logging in, there is no voting, there is no posting of things. This is a client built with their own API and it doesn't provide such a thing; at least not now and despite me seeing past promises that this will change, there's no API for doing that sort of thing.1
The idea of this application is you can run it up in the terminal, check the top, best and latest from the categories provided by the API, perhaps dive off into your web browser if needed, and then got on with other things.
It's there for when you're in the terminal you just need your hit of the orange site.
The main screen of the app revolves around the index of items, most of which are going to be stories. You can see an example of that above. For people who prefer things to be slightly less cramped, I've also added a "relaxed layout" mode too:
From the index you can head off into your web-browser by hitting Enter on any item; if the item is a story that links to somewhere that link will be opened; if it's something more like AskHN, or a job, it'll open the related page on HackerNews itself.
Pressing u with an item selected will let you view the details for the user who posted the item:
If you're the sort of person who wants to torture themselves by reading the comments (oh come on we all do it!), there's a comment reader/navigator too. With an item selected press c and the comment dialog will open:
I think the navigation within that dialog is fine; although I can see some scope for improvement. At the moment it uses a widget-per-comment (actually, it's at least 4 widgets per comment), which is fine and Textual handles that without an issue, even on items with lots of comments, but longer-term I can see me having some fun using the line API to build a super-efficient comment presentation and navigation widget.
That's it for now; it feels like a good v0.1.0 spot to be in. There are a bunch of things I still want to do with it (better cleaning up of the text, perhaps with some markup support so links get handled, etc; plus lots of ways of searching for stuff), but I felt it was in a place where I could start using it.
Yes, there are lots of clients that do all sorts of HTML-scraping of the actual website to make this possible; this ain't that. This ain't going to be that. ↩
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...
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:
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!")whilenotenvironment.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:
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.
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. ↩
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? ↩
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.
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:
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.
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.
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).
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:
There's now a playground with an emphasis on selecting widgets within containers1:
There's also now a playground that has an emphasis on pulling out widgets based on ID and classes:
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.
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. ↩
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. ↩
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.
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:
pipxinstalltextual-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.
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.
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 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.