mirror of
https://gitlab.com/EternalWanderer/voidcruiser.nl
synced 2024-11-28 20:03:50 +01:00
Start of prompt article and commit in child
This commit is contained in:
parent
9ae74b7140
commit
d15425526a
194
content/rambles/xmonad-prompts.md
Normal file
194
content/rambles/xmonad-prompts.md
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
---
|
||||||
|
title: "XMonad Promtps"
|
||||||
|
date: "2023-03-08T14:20:11+01:00"
|
||||||
|
author: "$HUMANOID"
|
||||||
|
tags: ["haskell", "xmonad", "linux"]
|
||||||
|
description: "There aren't a lot of instructions or explanatiosn on creating XMonad prompts, or at least not within a minute of checking my searx instance."
|
||||||
|
toc: true
|
||||||
|
---
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
XMonad has it's own prompt system. Some time ago, I wanted to see if it could
|
||||||
|
replace dmenu entirely. I managed it for the more common usages I had for it. My
|
||||||
|
application launcher, `ssh` prompt and pass interface were easy to replace using
|
||||||
|
standard XMonad Contrib modules (`XMonad.Prompt.Shell`, `XMonad.Prompt.Ssh` and
|
||||||
|
`XMonad.Prompt.Pass` respectively). However, things became more difficult when
|
||||||
|
it came to my universal/external Qutebrowser bookmarks menu and
|
||||||
|
`yt-dlp`-and-`pipe-viewer` wrapper.
|
||||||
|
|
||||||
|
# Bookmarks menu
|
||||||
|
|
||||||
|
The first one I decided to tackle was the bookmarks menu, as it is by far the
|
||||||
|
simplest of the two.
|
||||||
|
|
||||||
|
Let's take a look at the original:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/bin/sh
|
||||||
|
bookmarks="$HOME/.config/qutebrowser/bookmarks/urls"
|
||||||
|
choice="$(awk '{print$1}' $bookmarks | sort | dmenu -p "Bookmark:" -l 30)"
|
||||||
|
[ -z $choice ] || qutebrowser "$choice"
|
||||||
|
```
|
||||||
|
|
||||||
|
Things get interesting at the declaration of the `choice` variable:
|
||||||
|
|
||||||
|
1. It takes the contents of Qutebrowser's bookmarks file
|
||||||
|
2. It sorts the results of that
|
||||||
|
3. Sends that to `dmenu`, prompting the user to make a choice
|
||||||
|
|
||||||
|
|
||||||
|
After this, it checks whether `choice` is empty or not and in case it isn't,
|
||||||
|
opens Qutebrowser with its contents.
|
||||||
|
|
||||||
|
{{< start-details summary="Here is an example of how Qutebrowser saves its bookmarks" >}}
|
||||||
|
```
|
||||||
|
https://www.alpinelinux.org/ index | Alpine Linux
|
||||||
|
https://www.openbsd.org/ftp.html OpenBSD: Mirrors
|
||||||
|
https://commonmark.org/ CommonMark
|
||||||
|
https://xxiivv.com/ Echorridoors
|
||||||
|
https://100r.co/site/home.html 100R — home
|
||||||
|
https://solar.lowtechmagazine.com/about.html About this website | LOW←TECH MAGAZINE
|
||||||
|
```
|
||||||
|
{{< end-details >}}
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
Its functionality does boils down to the following:
|
||||||
|
|
||||||
|
1. Parse a given file according to a set of rules, returning it's contents in
|
||||||
|
the form of a list
|
||||||
|
2. Allow the user to make a choice from that list
|
||||||
|
3. Launch an application with that choice as parameter
|
||||||
|
|
||||||
|
Seems easy enough to implement.
|
||||||
|
|
||||||
|
### Parsing the Bookmarks file
|
||||||
|
|
||||||
|
Lets start off by creating a function that can parse our bookmarks file. Here we
|
||||||
|
need something to read a file -- in this case a bookmars file -- and return its
|
||||||
|
contents in the form of a list. So lets create a function that takes an arbitrary
|
||||||
|
file path and reads its contents, returning them as a list of strings.
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
fileContentList :: FilePath -> IO [String]
|
||||||
|
```
|
||||||
|
|
||||||
|
This function takes a filepath and returns `IO [String]`. This is to
|
||||||
|
accommodate that it has to read a file.
|
||||||
|
|
||||||
|
Now for the rest of the function:
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
fileContentList :: FilePath -> IO [String]
|
||||||
|
fileContentList f = do
|
||||||
|
homeDir <- getEnv "HOME"
|
||||||
|
file <- readFile (homeDir ++ "/" ++ f)
|
||||||
|
return . uniqSort . lines $ file
|
||||||
|
```
|
||||||
|
|
||||||
|
Lets go over what is happening here.
|
||||||
|
|
||||||
|
`fileContentList` is a function that takes an argument `f`.
|
||||||
|
|
||||||
|
First, it retrieves the current home directory based on the `$HOME` environment
|
||||||
|
variable and binds it to `homeDir` using the `getEnv` function from the
|
||||||
|
`System.Environment` module. `getEnv` returns a string with the contents of the
|
||||||
|
variable given as its argument.
|
||||||
|
|
||||||
|
Next, it retrieves the file contents from `$HOME/path/to/file` using the
|
||||||
|
`readFile`. This path is created by appending `f` to the `homeDir`.
|
||||||
|
|
||||||
|
Now for the final line.
|
||||||
|
|
||||||
|
First it takes the `file` and splits it up into a list of strings based on `\n`
|
||||||
|
using the `lines` function.
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
lines $ file
|
||||||
|
```
|
||||||
|
|
||||||
|
Then it pipes the result from that into `uniqSort` from the `XMonad.Prompt`
|
||||||
|
module in order to -- as the name implies -- sort it and get rid of any
|
||||||
|
duplicate items.
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
uniqSort . lines $ file
|
||||||
|
```
|
||||||
|
|
||||||
|
And the output of that is piped into `return`:
|
||||||
|
```haskell
|
||||||
|
return . uniqSort . lines $ file
|
||||||
|
```
|
||||||
|
|
||||||
|
This function will allows us to parse any given text file. To parse the
|
||||||
|
Qutebrowser bookmarks file, call it using `.config/qutebrowser/bookmarks/url`
|
||||||
|
|
||||||
|
### Creating a Prompt
|
||||||
|
Lets see if there is anything in the
|
||||||
|
[`XMonad.Prompt`](https://hackage.haskell.org/package/xmonad-contrib-0.17.1/docs/XMonad-Prompt.html)
|
||||||
|
module that looks like it could help us in creating a prompt.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
```haskell
|
||||||
|
mkXPrompt :: XPrompt p => p -> XPConfig -> ComplFunction -> (String -> X ()) -> X ()
|
||||||
|
```
|
||||||
|
Creates a prompt given:
|
||||||
|
- a prompt type, instance of the `XPrompt` class.
|
||||||
|
- a prompt configuration (`def` can be used as a starting point)
|
||||||
|
- a completion function (`mkComplFunFromList` can be used to create a completions function given a list of possible completions)
|
||||||
|
- an action to be run: the action must take a string and return `X ()`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This looks like it could serve as the basis for our prompt. The description and
|
||||||
|
type signature tell us that it is going to require an instance of the `XPrompt`
|
||||||
|
typeclass. So lets create a `Bookmark` datatype and implement the `showXPrompt`
|
||||||
|
function from `XPrompt` in order to give it a default message when executed and
|
||||||
|
thereby having it derive from `XPrompt`.
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
data Bookmark = Bookmark
|
||||||
|
|
||||||
|
instance XPrompt Bookmark where
|
||||||
|
showXPrompt Bookmark = "Bookmark: "
|
||||||
|
```
|
||||||
|
As its second argument, `mkXPrompt` requires an instance of `XPConfig`. The
|
||||||
|
`XPConfig` typeclass is where you -- as the name implies -- specify the
|
||||||
|
configuration of XMonad's prompts. Knowing this we can start to write function that
|
||||||
|
uses `mkXPrompt`:
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
bookmarkPrompt c = do
|
||||||
|
mkXPrompt Bookmark c
|
||||||
|
```
|
||||||
|
`c` is our `XPConfig` argument.
|
||||||
|
|
||||||
|
This takes care of the `XPrompt p => p -> XPConfig` portion of the function.
|
||||||
|
|
||||||
|
Now for the completion function, that will handle the list given to our prompt.
|
||||||
|
Lets mostly follow the suggestion in the description of `mkXPrompt` and lets
|
||||||
|
take a look at:
|
||||||
|
|
||||||
|
---
|
||||||
|
```haskell
|
||||||
|
mkComplFunFromList' :: XPConfig -> [String] -> String -> IO [String]
|
||||||
|
```
|
||||||
|
This function takes a list of possible completions and returns a completions
|
||||||
|
function to be used with mkXPrompt. If the string is null it will return all
|
||||||
|
completions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This is how Qutebrowser and `dmenu` act by default with a given list of possible
|
||||||
|
options.
|
||||||
|
|
||||||
|
So it takes an instance of `XPConfig` -- that will be our `c` argument, and a
|
||||||
|
list of strings. Here is where we feed it the contents of our file using our
|
||||||
|
`fileContentList` function.
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
bookmarkPrompt c = do
|
||||||
|
bl <- io fileContentList
|
||||||
|
mkXPrompt Bookmark c (mkComplFunFromList' c bl)
|
||||||
|
```
|
|
@ -1 +1 @@
|
||||||
Subproject commit 78d0d2d803187c7c26f36310e2692c4d7491ef7a
|
Subproject commit 9b04750cf5d8a891ee9f1e2b94b410ec3e947ab5
|
Loading…
Reference in a new issue