1
0
mirror of https://github.com/jbranchaud/til synced 2026-01-20 15:38:02 +00:00

Compare commits

..

17 Commits

Author SHA1 Message Date
Mohammad Alyetama
c5127aaaa6 Merge bc767a0ad3 into 138cab4fdc 2025-01-11 18:12:48 +09:00
jbranchaud
138cab4fdc Add Control Media With Drop Keyboard as a Workflow TIL 2025-01-10 16:16:01 -06:00
jbranchaud
5592d4266d Add Use A Different Font With iTerm2 as a Mac TIL 2025-01-09 11:13:17 -06:00
jbranchaud
daf448c1a5 Add Rebuild Tailwind Bundle For Dev Server as a Rails TIL 2025-01-08 19:39:41 -06:00
jbranchaud
aaddc35fcd Add Disclose Additional Details as an HTML TIL 2025-01-07 13:31:31 -06:00
jbranchaud
b575534d4e Add Difference Between Slice And Pointer To Slice as a Go TIL 2025-01-06 16:43:30 -06:00
jbranchaud
ae3ecbf72c Add Start Amphetamine Session With AppleScript as a Mac TIL 2025-01-05 19:03:26 -06:00
jbranchaud
1cf67b8f1a Add Configure Max String Print Length For Delve as a Go TIL 2025-01-04 13:07:13 -06:00
jbranchaud
f9c0a566eb Add See Where asdf Gets Current Tool Version as a Unix TIL 2025-01-03 12:11:02 -06:00
jbranchaud
527038ca23 Fix TIL count, it was off by 1 2025-01-02 13:58:26 -06:00
jbranchaud
b972673008 Add Simon Willison's TIL to list of other TILs 2025-01-02 13:54:50 -06:00
jbranchaud
cc31aae25a Update copyright date to 2025, time flies 2025-01-02 13:52:57 -06:00
jbranchaud
26f30c3225 Update README with a few learning resource links 2025-01-02 13:52:24 -06:00
jbranchaud
e14da2f207 Add Basic Delve Debugging Session as a Go TIL 2025-01-02 13:45:16 -06:00
jbranchaud
b7d4a62ecb Add Refer To Implicit Block Argument With It as a Ruby TIL 2025-01-01 12:16:53 -06:00
jbranchaud
1ad41b9776 Add Connect To A SQLite Database as a Go TIL 2024-12-31 10:48:01 -06:00
jbranchaud
11716a8fb5 Add Install Latest Version Of Ruby With asdf as a Ruby TIL 2024-12-30 19:20:33 -07:00
13 changed files with 480 additions and 3 deletions

View File

@@ -10,7 +10,11 @@ pairing with smart people at Hashrocket.
For a steady stream of TILs, [sign up for my newsletter](https://crafty-builder-6996.ck.page/e169c61186).
_1552 TILs and counting..._
_1562 TILs and counting..._
See some of the other learning resources I work on:
- [Ruby Operator Lookup](https://www.visualmode.dev/ruby-operators)
- [Vim Un-Alphabet](https://www.youtube.com/playlist?list=PL46-cKSxMYYCMpzXo6p0Cof8hJInYgohU)
---
@@ -403,12 +407,16 @@ _1552 TILs and counting..._
- [Access Go Docs Offline](go/access-go-docs-offline.md)
- [Add A Method To A Struct](go/add-a-method-to-a-struct.md)
- [Basic Delve Debugging Session](go/basic-delve-debugging-session.md)
- [Build For A Specific OS And Architecture](go/build-for-a-specific-os-and-architecture.md)
- [Check If Cobra Flag Was Set](go/check-if-cobra-flag-was-set.md)
- [Combine Two Slices](go/combine-two-slices.md)
- [Configure Max String Print Length For Delve](go/configure-max-string-print-length-for-delve.md)
- [Connect To A SQLite Database](go/connect-to-a-sqlite-database.md)
- [Create A Slice From An Array](go/create-a-slice-from-an-array.md)
- [Detect If Stdin Comes From A Redirect](go/detect-if-stdin-comes-from-a-redirect.md)
- [Deterministically Seed A Random Number Generator](go/deterministically-seed-a-random-number-generator.md)
- [Difference Between Slice And Pointer To Slice](go/difference-between-slice-and-pointer-to-slice.md)
- [Do Something N Times](go/do-something-n-times.md)
- [Find Executables Installed By Go](go/find-executables-installed-by-go.md)
- [Format Date And Time With Time Constants](go/format-date-and-time-with-time-constants.md)
@@ -446,6 +454,7 @@ _1552 TILs and counting..._
- [Adding Alt Text To An Image](html/adding-alt-text-to-an-image.md)
- [Determine Which Button Submitted The Form](html/determine-which-button-submitted-the-form.md)
- [Disable Auto-Completion For A Form Input](html/disable-auto-completion-for-a-form-input.md)
- [Disclose Additional Details](html/disclose-additional-details.md)
- [Make Elements Non-Interactive With Inert](html/make-elements-non-interactive-with-inert.md)
- [Prevent Search Engines From Indexing A Page](html/prevent-search-engines-from-indexing-a-page.md)
- [Render Text As Superscript](html/render-text-as-superscript.md)
@@ -654,6 +663,8 @@ _1552 TILs and counting..._
- [Run AppleScript Commands Inline In The Terminal](mac/run-applescript-commands-inline-in-the-terminal.md)
- [Set A Window To Its Default Zoom Level](mac/set-a-window-to-its-default-zoom-level.md)
- [Specify App When Opening From Command Line](mac/specify-app-when-opening-from-command-line.md)
- [Start Amphetamine Session With AppleScript](mac/start-amphetamine-session-with-applescript.md)
- [Use A Different Font With iTerm2](mac/use-a-different-font-with-iterm2.md)
- [Use Default Screenshot Shortcuts With CleanShot X](mac/use-default-screenshot-shortcuts-with-cleanshot-x.md)
- [View All Windows Of The Current App](mac/view-all-windows-of-the-current-app.md)
- [Write System Clipboard To A File](mac/write-system-clipboard-to-a-file.md)
@@ -1027,6 +1038,7 @@ _1552 TILs and counting..._
- [Query A Single Value From The Database](rails/query-a-single-value-from-the-database.md)
- [Read In Environment-Specific Config Values](rails/read-in-environment-specific-config-values.md)
- [Read-Only Models](rails/read-only-models.md)
- [Rebuild Tailwind Bundle For Dev Server](rails/rebuild-tailwind-bundle-for-dev-server.md)
- [Remove A Database Column From A Table](rails/remove-a-database-column-from-a-table.md)
- [Remove The Default Value On A Column](rails/remove-the-default-value-on-a-column.md)
- [Render An Alternative ActionMailer Template](rails/render-an-alternative-action-mailer-template.md)
@@ -1269,6 +1281,7 @@ _1552 TILs and counting..._
- [Iterate With An Offset Index](ruby/iterate-with-an-offset-index.md)
- [Include Extra Context In A Honeybadger Notify](ruby/include-extra-context-in-a-honeybadger-notify.md)
- [Ins And Outs Of Pry](ruby/ins-and-outs-of-pry.md)
- [Install Latest Version Of Ruby With asdf](ruby/install-latest-version-of-ruby-with-asdf.md)
- [Invoking Rake Tasks Multiple Times](ruby/invoking-rake-tasks-multiple-times.md)
- [IRB Has Built-In Benchmarking With Ruby 3](ruby/irb-has-built-in-benchmarking-with-ruby-3.md)
- [Jump Out Of A Nested Context With Throw/Catch](ruby/jump-out-of-a-nested-context-with-throw-catch.md)
@@ -1301,6 +1314,7 @@ _1552 TILs and counting..._
- [Question Mark Operator](ruby/question-mark-operator.md)
- [Rake Only Lists Tasks With Descriptions](ruby/rake-only-lists-tasks-with-descriptions.md)
- [Read The First Line From A File](ruby/read-the-first-line-from-a-file.md)
- [Refer To Implicit Block Argument With It](ruby/refer-to-implicit-block-argument-with-it.md)
- [Rendering ERB](ruby/rendering-erb.md)
- [Replace The Current Process With An External Command](ruby/replace-the-current-process-with-an-external-command.md)
- [Require Entire Gemfile In Pry Session](ruby/require-entire-gemfile-in-pry-session.md)
@@ -1563,6 +1577,7 @@ _1552 TILs and counting..._
- [Search History](unix/search-history.md)
- [Search Man Page Descriptions](unix/search-man-page-descriptions.md)
- [Securely Remove Files](unix/securely-remove-files.md)
- [See Where asdf Gets Current Tool Version](unix/see-where-asdf-gets-current-tool-version.md)
- [Set The asdf Package Version For A Single Shell](unix/set-the-asdf-package-version-for-a-single-shell.md)
- [Show A File Preview When Searching With FZF](unix/show-a-file-preview-when-searching-with-fzf.md)
- [Show Disk Usage For The Current Directory](unix/show-disk-usage-for-the-current-directory.md)
@@ -1856,11 +1871,11 @@ I shamelessly stole this idea from
* [Today I Learned by Hashrocket](https://til.hashrocket.com)
* [jwworth/til](https://github.com/jwworth/til)
* [thoughtbot/til](https://github.com/thoughtbot/til)
* [til.simonwillison.net](https://til.simonwillison.net/)
## License
© 2015-2022 Josh Branchaud
© 2015-2025 Josh Branchaud
This repository is licensed under the MIT license. See `LICENSE` for
details.

View File

@@ -0,0 +1,63 @@
# Basic Delve Debugging Session
When using [delve](https://github.com/go-delve/delve) to debug a Go program,
these are the series of things I usually find myself doing.
First, I start running the program with `dlv` including any arguments after a `--` (in my case, the `solve` subcommand and a filename).
```bash
$ dlv debug . -- solve samples/001.txt
```
`dlv` starts up and is ready to run my program from the beginning. I'll need to
set a couple breakpoints before continuing. I do this with the `break` command,
specifying the filename and line number.
```
(dlv) break main.go:528
Breakpoint 1 set at 0x10c1a5bea for main.traversePuzzleIterative() ./main.go:528
(dlv) break main.go:599
Breakpoint 2 set at 0x10c1a6dcc for main.traversePuzzleIterative() ./main.go:599
```
Now I can continue which will run the program until hitting a breakpoint.
```
(dlv) continue
> [Breakpoint 2] main.traversePuzzleIterative() ./main.go:599 (hits goroutine(1):1 total:1) (PC: 0x10c1a6dcc)
594: }
595: }
596:
597: topStackFrame := stack[len(stack)-1]
598: // if the current stack frame has more values, try the next
=> 599: if len(topStackFrame.PossibleValues) > 0 {
600: nextValue := topStackFrame.PossibleValues[0]
601: topStackFrame.PossibleValues = topStackFrame.PossibleValues[1:]
602: topStackFrame.CurrValue = nextValue
603:
604: // Undo the last placement and make a new one
```
I can see the context around the line we've stopped on. From here I can dig
into the current state of the program by looking at local variables (`locals`)
or printing out a specific value (`print someVar`). I can continue to step
through the program line by line with `next` or eventually run `continue` to
proceed to the next breakpoint.
```
(dlv) locals
diagnostics = main.Diagnostics {BacktrackCount: 0, NodeVisitCount: 1, ValidityCheckCount: 2,...+2 more}
stack = []main.StackData len: 1, cap: 1, [...]
emptyCellPositions = [][]int len: 3, cap: 4, [...]
emptyCellIndex = 1
status = "Invalid"
topStackFrame = main.StackData {RowIndex: 1, ColumnIndex: 7, PossibleValues: []int len: 8, cap: 8, [...],...+1 more}
(dlv) print topStackFrame
main.StackData {
RowIndex: 1,
ColumnIndex: 7,
PossibleValues: []int len: 8, cap: 8, [2,3,4,5,6,7,8,9],
CurrValue: 1,}
(dlv) next
> main.traversePuzzleIterative() ./main.go:600 (PC: 0x10c1a6dea)
```

View File

@@ -0,0 +1,29 @@
# Configure Max String Print Length For Delve
During a [Delve](https://github.com/go-delve/delve) debugging session, we can
print out the value of a given variable with the `print` command. Similarly, we
can see the values of all local variables with the `locals` command.
Whenever Delve is printing out strings and slices, it will truncate what it
displays to 64 characters (or items) by default.
```go
(dlv) print diagnostics.Solutions[0]
"295743861\n431865972\n876192543\n387459216\n612387495\n549216738\n7635...+25 more"
```
This can be overridden by [changing the `config` of
`max-string-len`](https://github.com/derekparker/delve/blob/237c5026f40e38d2dd6f62a7362de7b25b00c1c7/Documentation/cli/expr.md?plain=1#L59)
to something longer. In my case here, all I need are about 90 characters to
display my full string, so run `config max-string-len 90` from the `dlv`
session.
```go
(dlv) config max-string-len 90
(dlv) print diagnostics.Solutions[0]
"295743861\n431865972\n876192543\n387459216\n612387495\n549216738\n763524189\n928671354\n154938627"
```
Now I can see the entire string instead of the truncated version.
[source](https://stackoverflow.com/a/52416264/535590)

View File

@@ -0,0 +1,50 @@
# Connect To A SQLite Database
Using the `database/sql` module and the `github.com/mattn/go-sqlite3` package,
we can connect to a SQLite database and run some queries. In my case, I have a
SQLite connection string exported to my environment, so I can access that with
`os.Getenv`. It's a local SQLite file, `./test.db`.
Calling `sql.Open`, I'm able to connect with a SQLite3 driver to the database
at that connection string. The `setupDatabase` function returns that database
connection pointer. Things like `Exec` and `QueryRow` can be called on `db`. I
also need to make sure I close the connection to the database with a `defer`.
Here is a full example of connecting to a local SQLite database and inserting a
record:
```go
package main
import (
"database/sql"
"fmt"
"os"
_ "github.com/mattn/go-sqlite3"
)
func setupDatabase() *sql.DB {
databaseString := os.Getenv("GOOSE_DBSTRING")
if len(databaseString) == 0 {
fmt.Println("Error retrieving `GOOSE_DBSTRING` from env")
os.Exit(1)
}
db, err := sql.Open("sqlite3", databaseString)
if err != nil {
fmt.Printf("Error opening database: %v\n", err)
os.Exit(1)
}
return db
}
func main() {
db := setupDatabase()
defer db.Close()
sql := `insert into users (name) values (?);`
db.Exec(sql, "Josh")
}
```

View File

@@ -0,0 +1,55 @@
# Difference Between Slice And Pointer To Slice
Though a slice can be thought of and used as a flexible, variable-length
array-like data structure, it is important to understand that it is also a
special kind of pointer to an underlying array.
This matters when we a function receives a slice versus a pointer to a slice as
an argument, depending on what it is doing with that slice.
If the function is access or updating elements in the slice, there is no
difference. There is no meaningful difference between these two functions and
we might as well use the former.
```go
func replaceAtIndex(slice []string, index int, value string) {
slice[index] = value
}
func replaceAtIndexPtr(slice *[]string, index int, value string) {
(*slice)[index] = value
}
```
On the other hand, if the receiving function needs to append to or replace the
slice, then we need to pass a pointer to the slice. A direct slice argument
will result in only the function-local copy getting replaced.
```go
package main
import (
"fmt"
)
func main() {
s1 := []int{8, 6, 7, 9}
s2 := []int{8, 6, 7, 9}
addItem(s1, 11)
fmt.Printf("s1: %v\n", s1) //=> s1: [8 6 7 9]
addItemPtr(&s2, 11)
fmt.Printf("s2: %v\n", s2) //=> s2: [8 6 7 9 11]
}
func addItem(slice []int, value int) {
slice = append(slice, value)
}
func addItemPtr(slice *[]int, value int) {
(*slice) = append(*slice, value)
}
```
[source](https://go.dev/tour/moretypes/8)

View File

@@ -0,0 +1,28 @@
# Disclose Additional Details
You can add extra details to an HTML page that are only disclosed if the user
chooses to disclose them. To do that, we use the `<details>` tag. This tag
needs to have a `<summary>` tag nested within it. Anything else nested within
`<details>` will be what is disclosed when it is toggled open. The `<summary>`
is what is displayed when it is not open.
Here is a `<detail>` block I recently added to [Ruby Operator
Lookup](https://www.visualmode.dev/ruby-operators).
```html
<details className="pt-2 pb-6">
<summary>What is this thing?</summary>
<p className="pl-3 pt-2 text-gray-700 text-sm">
Ruby is an expressive, versatile, and flexible dynamic programming language. That means there are all kinds of syntax features, operators, and symbols we can encounter that might look unfamiliar and are hard to look up. Ruby Operator Lookup is a directory of all these language features.
</p>
<p className="pl-3 pt-2 text-gray-700 text-sm">
Use the search bar to narrow down the results. Then click on a button for the operator or symbol you want to explore further.
</p>
</details>
```
On page load, the only thing we see is "What is this thing?" with a triangle
symbol next to it. If we click the summary, then the entire details block
(those two `<p>` tags) are disclosed.
[source](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details)

View File

@@ -0,0 +1,37 @@
# Start Amphetamine Session With AppleScript
I use the _Amphetamine_ app on Mac to keep my computer from going to sleep
during the day. It is a menu bar app that can be used to start a _Session_ of
time where it will keep your computer from going to sleep. At the start of my
day, I'll typically start an 8 hour _Session_. This is useful if I have to step
away fo 10 minutes or if I'm doing some writing in my notebook, my computer
won't go to sleep on me.
Though these sessions can be controlled from the menu bar app, I was excited to
learn that I can also programatically start a session with AppleScript.
Here is how to start a _Session_ (overriding an existing session) with options
that specify it is 8 hours long and the display should not be allowed to sleep.
```bash
$ osascript -e 'tell application "Amphetamine" to start new session with options {duration:8, interval:hours, displaySleepAllowed:false}'
```
The `interval` could also be `minutes` and then I could change the duration to
an amount of time that makes sense in minutes, e.g. `90` for 1.5 hours.
Note: the `with options {...}` segement is all or nothing. All three need to be included or don't include the clause at all.
Additionally, a session of indefinite duration can be started by including no options:
```bash
$ osascript -e 'tell application "Amphetamine" to start new session'
```
And any existing session can be ended with:
```bash
$ osascript -e 'tell application "Amphetamine" to end session'
```
[source](https://iffy.freshdesk.com/support/solutions/articles/48000078223-applescript-documentation)

View File

@@ -0,0 +1,25 @@
# Use A Different Font With iTerm2
I wanted to give [`gh-dash`](https://github.com/dlvhdr/gh-dash) a try, but
after installing and opening it up, I was seeing a bunch of `?` characters
where specialized font icons were missing. Their README recommended installing
a [`Nerd Font`](https://github.com/ryanoasis/nerd-fonts) that includes those
icons, such as [`Fira Code`](https://github.com/tonsky/FiraCode).
I was able to install `font-fira-code-nerd-font` with homebrew:
```bash
$ brew install font-fira-code-nerd-font
```
Then to get iTerm2 to start using that font, I had to change the font setting
for my current profile.
Under the _iTerm2_ menu is _Settings..._. From there, I clicked the _Profiles_
section. For the _Default_ profile, I went to the _Text_ tab and under _Font_ I
selected _FireCode Nerd Font Mono_ from the dropdown.
That won't take effect on any current iTerm2 windows. Since I have everything
running through `tmux`, I could close my current window, open a new one
(`Cmd+N`), and reconnect to my existing `tmux` session. Now when I run `gh
dash`, I see all the font icons that were missing before.

View File

@@ -0,0 +1,29 @@
# Rebuild Tailwind Bundle For Dev Server
If you're using the TailwindCSS gem in your Rails app:
```ruby
# Use Tailwind CSS [https://github.com/rails/tailwindcss-rails]
gem "tailwindcss-rails"
```
you may find that as you add and adjust styles in your views, refreshing the
page doesn't take any styling effects. That is because the tailwind bundle gets
built with just the style rules that were used at the time it was generated.
In development, as we're working, we expect the styles used by our app to
actively changed. And we don't mind a little performance hit to have the bundle
rebuilt. In that case, we can instruct `puma` to _Live Rebuild_ in
`development` with the `tailwindcss` plugin.
```ruby
# config/puma.rb
# Enable TailwindCSS rebuild in development
plugin :tailwindcss if ENV.fetch("RAILS_ENV", "development") == "development"
```
This has `rails server` run a watch process in the background that live
rebuilds the bundle.
[source](https://github.com/rails/tailwindcss-rails?tab=readme-ov-file#puma-plugin)

View File

@@ -0,0 +1,54 @@
# Install Latest Version Of Ruby With asdf
When I check the `asdf` Ruby plugin for known versions of Ruby:
```bash
$ asdf list-all ruby | fzf
```
I don't find the latest (`3.4`).
I need to update the plugin. A newer version of the plugin will know about
newer Ruby versions.
```bash
$ asdf plugin-update ruby
```
Now, if I run the `list-all` command again, I'll find the version I'm looking
for — `3.4.1`.
Now that `asdf` and I both know about the version to be installed, I can tell
`asdf` to install it:
```bash
$ asdf install ruby 3.4.1
```
Now, if I check the current Ruby version, I'll see that it is still set to some
other version.
```bash
$ ruby --version
ruby 3.2.2 (2023-03-30 revision e51014f9c0) [x86_64-darwin22]
```
I need to tell `asdf` to start using this newly installed version instead,
either globally or locally.
```bash
$ # globally
$ asdf global ruby 3.4.1
$ # or locally
$ asdf local ruby 3.4.1
```
And now I'm all set:
```bash
$ asdf current ruby
ruby 3.4.1 /Users/jbranchaud/.tool-versions
$ ruby --version
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [x86_64-darwin22]
```

View File

@@ -0,0 +1,43 @@
# Refer To Implicit Block Argument With It
One of the key features of the Ruby 3.4 release is the `it` implicit block
argument.
The vast majority of inline blocks defined in Ruby code receive a single block
argument. Typically we name and reference a block argument explictly like so:
```ruby
items.map { |item| item * item }
```
Ruby likes to cut away excess syntax when possible. To that end, the implicit
`it` block argument has been added. This is an identifier we can reference in
the context of a block and its value is the current
```ruby
items = [1,2,3,4,5]
squares = items.map { it * it }
pp squares
#=> [1, 4, 9, 16, 25]
```
Note: we cannot mix numbered parameters (`_1`, `_2`) with the `it` parameter.
If we do, we'll get the following error:
```ruby
def method_using_block(a, b)
yield(a, b) if block_given?
end
puts method_using_block(4,5) { _2 ** _1 } #=> 625
puts method_using_block(4,5) { _2 ** it }
# it_block.rb:12: syntax error found (SyntaxError)
# 10 |
# 11 | puts method_using_block(4,5) { _2 ** _1 }
# > 12 | ... it }
# | ^~ `it` is not allowed when a numbered parameter is already used
```
[source](https://docs.ruby-lang.org/en/3.4/NEWS_md.html)

View File

@@ -0,0 +1,29 @@
# See Where asdf Gets Current Tool Version
The other day I [installed the latest version of
Ruby](ruby/install-latest-version-of-ruby-with-asdf.md) with `asdf`. I then set
that version (`3.4.1`) as the global default. However, when I then ran `ruby
--version`, I was getting a `3.2.x` version. I checked my current project's
directory and there was no `.tool-versions` file, so it wasn't being set by my
current directory.
`asdf` looks up the current chain of directories until it encounters a
`.tool-versions` file, so it must have been finding one somewhere up there, but
before it was getting to the _global_ `.tool-versions` file. But where?
The `asdf current` command can tell us for a specific tool what the current
version it is set to and what file is giving that directive.
```bash
asdf current ruby
ruby 3.2.2 /Users/jbranchaud/code/.tool-versions
```
As it turns out, I had a `.tool-versions` file in `$HOME/code` that was setting
that `3.2.x` Ruby version.
I didn't want that directory controlling the Ruby version, so I removed `ruby`
from that file. `asdf` was then able to traverse up to `$HOME/.tool-versions`
for the global setting.
See `asdf help` for more details.

View File

@@ -0,0 +1,20 @@
# Control Media With Drop Keyboard
I have a [Drop CTRL](https://drop.com/buy/drop-ctrl-v2-mechanical-keyboard)
mechanical keyboard which mostly works like any other keyboard. It also has a
set of functionality that can be accessed via the `fn` (function) key. The
function key can be used to configure the keyboard's LEDs, but I tend to set
and forget that.
Instead, I like to use the function key to control media. That is, adjust the
volume, play and pause, and skip to the next song.
Here is a listing of the ones I use:
- `Fn + Insert` to Pause / Play
- `Fn + PgUp/PgDown` to Increase / Decrease the volume
- `Fn + Del/End` to go to the Previous / Next song
Here is a [full listing of the function
keys](https://drop.com/talk/9382/how-to-configure-your-drop-keyboard) for Drop
keyboards.