A place where I capture raw, quick notes worth remembering.

August 23, 2023

vim wsl

Copy/Paste for Neovim in WSL

If you want a seamless copy/paste experience on Neovim in WSL there is a newly recommended method for doing so, without having to install extra software.

Paste the following Lua snippet in your configuration and "+y to yank the selected text into your global register. Similarly, use "+p to paste.

if vim.fn.has "wsl" == 1 then
  vim.g.clipboard = {
    name = "WslClipboard",
    copy = {
      ["+"] = "clip.exe",
      ["*"] = "clip.exe",
    },
    paste = {
      ["+"] = 'powershell.exe -c [Console]::Out.Write($(Get-Clipboard -Raw).tostring().replace("`r", ""))',
      ["*"] = 'powershell.exe -c [Console]::Out.Write($(Get-Clipboard -Raw).tostring().replace("`r", ""))',
    },
    cache_enabled = 0,
  }
end

Permalink

August 22, 2023

windows wsl

Faster key repeat on Windows

With WSL, Windows can be a great developer experience. The one thing which I did find lacking, especially when moving around in Vim, is that the maxixmum key repeat rate in the configuration panel is too slow.

I was able to fix that with the following settings in Regedit:

[HKEY_CURRENT_USER\Control Panel\Accessibility\Keyboard Response]
"AutoRepeatDelay"="240" 
"AutoRepeatRate"="12" 
"DelayBeforeAcceptance"="0" 
"Flags"="59" 
"BounceTime"="0"

You have to login and out again for it to take effect.

Permalink

August 22, 2023

vim linux wsl

Latest Neovim on Ubuntu

I’m using Ubuntu for my development environment, and Ubuntu does not have the latest Neovim in their repository.

That’s why I choose to install the last version by source. The steps I take are:

wget https://github.com/neovim/neovim/releases/download/v0.9.1/nvim-linux64.tar.gz
tar xfz nvim-linux64.tar.gz
sudo mv nvim-linux64 /opt/nvim

Now you have to make sure that /opt/nvim/bin is part of your path. If you are smart, and using the Fish shell, add this to your Fish config.

fish_add_path -aP /opt/nvim/bin

If you are on Bash:

export PATH=/opt/nvim/bin:$PATH

Enjoy your latest version of Neovim!

Permalink

July 9, 2023

linux alacritty

Alacritty as default terminal

I’m using Alacritty as my terminal of choice on Ubuntu (Pop!OS). When installed manually, it does not work with the hotkey of Pop!OS because you can’t set it as the default terminal.

For that to work, you have to manually add it as an alternative and set it:

sudo update-alternatives --install /usr/bin/x-terminal-emulator x-terminal-emulator $(which alacritty) 50
sudo update-alternatives --config x-terminal-emulator

Permalink

July 5, 2023

vim

Patching Berkeley Mono with Nerd Fonts

I’m using Berkeley Mono as my font, which is beautiful, but doesn’t have all the glyphs I need for my terminal and editor.

The glyphs I need are part of Nerd Fonts. This is the command I run to patch Berkeley Mono with the use of Docker:

docker run --rm \
	-v /tmp/berkeley-mono/origin:/in \
	-v /tmp/berkeley-mono/patched:/out \
	nerdfonts/patcher \
	--progressbars \
	--mono \
	--adjust-line-height \
	--fontawesome \
	--fontawesomeextension \
	--fontlogos \
	--octicons \
	--codicons \
	--powersymbols \
	--pomicons \
	--powerline \
	--powerlineextra \
	--material \
	--weather

I found the above command in this blog post from Serhat Teker: Patching Fonts with Docker.

Permalink

June 9, 2023

python

Working with Python

I’m exploring the world of Machine Learning, and in that world, Python is king.

Since it had been a decade since I worked with the language, I had to set up a good development environment, starting with installing Python.

For system-wide Python, I resort to my trusty asdf version manager. It’s a great tool that allows me to install multiple versions of Python and switch between them easily.

I also want pyright and black installed with each Python installation, so I first create a file in my home directory called .default-python-packages with the following contents:

pyright
black

Then I run the following commands to install the plugin, install Python 3.11.4, and set it as the default:

asdf plugin add python
asdf install python 3.11.4
asdf global python 3.11.4

This manages my global Python installation, which I use for standalone scripts.

However, I need to set up a virtual environment to work on a project with dependencies. For that, I use a new tool called Rye. Rye manages everything, including the Python version – so I won’t use asdf for that.

After installing Rye, I can create a new virtual environment with the following:

rye init my-project

This will create a new directory called my-project with a rye.toml file in it. This file contains the Python version and the default packages to install. I can add more packages to it, like langchain:

cd my-project
rye add langchain

This only adds the dependency but does not install it. For that, we need to run rye sync.

That’s it! Now we have ASDF for our global Python and Rye for our projects in Python. Enjoy!

Permalink

June 5, 2023

life

3, 2, 1... Sleep

I would always deprioritize sleep, stay up late, wake up early. And honestly, it was one of the most stupid things I did.

These days I make sure to get at least 7 hours of sleep. So that’s for the duration of sleep, doesn’t say much on the quality though.

So for quality I have this simple 3, 2, 1 rule:

  • 3 hours before sleep: no food.
  • 2 hours before sleep: no drinks.
  • 1 hour before sleep: no screens.

I’ve been doing this for a while now and it significantly improved my sleep quality. I wake up more rested and I’m more productive during the day.

Permalink

May 20, 2023

fish mac

Setting up Fish on the Mac

Fish shell is my shell of choice and having it setup as default on the Mac requires some extra steps.

When homebrew is installed, run brew install fish.

After that, edit /etc/shells and add fish to it:

# sudo edit /etc/shells

Add the bottom of the file add:

/opt/homebrew/bin/fish

Then set the shell as default with:

chsh -s /opt/homebrew/bin/fish

It does require logging again to be activated.

Permalink

February 27, 2023

mac alacritty

Crisp fonts on Alacritty

screenshot of my Alacritty setup on the Mac

I’m using Alacritty on the Mac and I noticed how the font rendering is much thicker than I’m used to on iTerm. On iTerm I use the “thin strokes” setting, which is not available in Alacritty.

Turns out, you can set it with:

defaults write org.alacritty AppleFontSmoothing -int 0

If you then log in and logout again, your Alacritty will be similar to the crispness you find in iTerm.

If you want to restore it back to the default, do:

defaults delete org.alacritty AppleFontSmoothing

And if you like to have this kind of crispness across the entire OS. Do:

defaults write -g AppleFontSmoothing -int 0

Permalink

February 27, 2023

mac

Faster key repeat on the Mac

I like to have my key repeat set high, because I still browse my code line, by line; yes, I know there are better ways to browse.

On the Mac, if you go through the “System Preferences”, there is only so much you can do. Luckily, you can get the right settings if you use the terminal.

First, make sure that you have disabled “Slow keys” in “System Preferences > Accessibility > Keyboard > Slow keys”.

Then go into your Terminal of choice and type:

defaults write -g KeyRepeat -int 1
defaults write -g InitialKeyRepeat -int 12

The normal minimum of KeyRepeat is 2. Setting it to 1 will have a key repeat delay of 15ms.

For the InitialKeyRepeat, the minimum is 10; 15 if you use system settings. I prefer to set it to 12.

If you want to restore the defaults, just go through the system settings and set it to a value you prefer.

Permalink

February 17, 2023

linux java

Java with Adoptium on Linux

Recently I wrote down how to get Java installed on MacOS with the help of Adoptium. Well, it turns out, I also use Linux (Pop!_OS if you were wondering).

To install Java on Linux, go to the Adoptium page and download the tarball according to the version and your architecture. I downloaded the LTS 19 for x64:

tar xfz OpenJDK19U-jdk_x64_linux_hotspot_19.0.2_7.tar.gz
sudo mv jdk-19.0.2+7 /opt/

And now make sure that your shell can find it, my shell is Fish, so I added this snippet:

# Java installation through Adoptium
set -l java_version "19.0.2+7"
if test -d "/opt/jdk-$java_version"
    set -x JAVA_HOME "/opt/jdk-$java_version"
    fish_add_path -aP "$JAVA_HOME/bin"
end

Now you have Java, quickly go install Clojure :)

Permalink

February 15, 2023

linux utils

Espanso, text expander for Linux

I love the smart dashes – and I use them heavily – on the Mac and it’s something I missed on PopOS!.

Luck be it, Espanso comes along. Installation on a Debian system is easy.

Configuration of Espanso is done by editing configuration files located in ~/.config/espanso directory. You can also find it by tying espanso path. The files contained in the match directory define what Espanso should do. The files contained in the config directory define how Espanso should perform its expansions.

To replace our double dashes with a long dash we are going to edit $CONFIG/match/base.yml add change it to:

matches:
  - trigger: "--"
    replace: ""

However, now this also happens in our terminal, which is annoying, so we are going to disable Espanso completely in the terminal with an app specific configuration.

We’ll disable it in Alacritty, my terminal of choice by creating a config/alacritty.yml and match on the class name:

filter_class: Alacritty
enable: false

That’s it, Espanso is super powerful and I can recommend reading the docs on what else it can do. Oh, and it also works on MacOS or Windows.

Permalink

February 6, 2023

clojure mac

Installing Clojure on a Mac

I had to set up a new laptop recently, and one of the first things you install on a new laptop is Clojure.

Below is the setup I used and which got me started quickly. First, you must install Homebrew as a package manager.

Second, we also need Java, and the best option out there is supplied by Adoptium, which has pre-built OpenJDK binaries for you. Go to the website and download the latest LTS release if you want to be safe, or download the newest version (19 as of writing this) if you’re going to live on the edge.

After that, install Clojure is one command away:

brew install clojure/tools/clojure

Type clj on the command line, and when running for the first time, it will download the required packages and give you access to the REPL.

Now, for starting and managing projects, neil is an amazing new CLI which can help you do that. Install it with:

brew install babashka/brew/neil

Permalink

December 27, 2022

git

A quick gitignore from a template

Often when I start a new project, I also need to have a .gitignore for the specific language that I’m working in.

To easily do this, I added the following alias to my git, which pulls a template from gitignore.io:

git config --global alias.ignore \
'!gi() { curl -sL https://www.toptal.com/developers/gitignore/api/$@ ;}; gi'

Now you can do git ignore zig and it will show you the default ignore file for a Zig project.

To automatically put it in your gitignore, just redirect the output:

git ignore zig >> .gitignore

Permalink

December 13, 2022

zig

Easily install Zig with zigup

I used to manage my Zig installation by installing the binary and copying to my path. Not too hard, but now there is even an easier way by using zigup.

To install zig, it enables you to simply type zigup master

I would recommend to get the latest binary from the Github releases page. Or if you already have Zig, install it from source:

# Install the binary
git clone [email protected]:marler8997/zigup.git
cd zigup
zig build -Dfetch
cp zig-out/bin/zigup ~/.local/bin/

Permalink

November 26, 2022

helix

Language Servers for Helix

I have been trying out Helix as my daily editor and the editor has built in support for LSP. However, it does not install the Language Servers, so you have to do that yourself.

Below are the language servers I installed for writing my code in HTML, CSS and Markdown.

You can always check if a language is setup by doing:

hx --health <lang>

So for example this will tell you if your HTML is supported:

hx --health html

HTML, CSS and SCSS

Both HTML, CSS and SCSS use the language server from Visual Studio Code:

npm i -g vscode-langservers-extracted

Markdown

For Markdown it uses marksman and to install it you can download the latest version from their releases page

wget <link from releases page>

# Make it executable
chmod +x marksman-<dist>

# Move it to a directory in your path, in my case `~/.local/bin`
mv marksman-<dist> ~/.local/bin/marksman

Move it to somewhere in your $PATH and you should be good to go.

Permalink

November 24, 2022

zig

Zig types explained in Ziglings

I have been tinkering with Zig lately and to get a grasp on the language I have been using the excellent Ziglings quizes.

In Quiz #58 there is a great comment which lists the different types for Zig:

//
// We've absorbed a lot of information about the variations of types
// we can use in Zig. Roughly, in order we have:
//
//                          u8  single item
//                         *u8  single-item pointer
//                        []u8  slice (size known at runtime)
//                       [5]u8  array of 5 u8s
//                       [*]u8  many-item pointer (zero or more)
//                 enum {a, b}  set of unique values a and b
//                error {e, f}  set of unique error values e and f
//      struct {y: u8, z: i32}  group of values y and z
// union(enum) {a: u8, b: i32}  single value either u8 or i32
//
// Values of any of the above types can be assigned as "var" or "const"
// to allow or disallow changes (mutability) via the assigned name:
//
//     const a: u8 = 5; // immutable
//       var b: u8 = 5; //   mutable
//
// We can also make error unions or optional types from any of
// the above:
//
//     var a: E!u8 = 5; // can be u8 or error from set E
//     var b: ?u8 = 5;  // can be u8 or null

Permalink

November 20, 2022

git

Create a new repository with Github CLI

Github has their own CLI tool which enables you to interact with Github through the command line.

I often start new repositories and started using the following command to quickly create a accompanying, private repo on Github:

gh repo create <name> --private --source=. --remote=origin

Permalink

October 14, 2022

emacs mac

Install Emacs on the Mac

There are many ways to install Emacs on the Mac, from pre-build Applications, to Homebrew, to installing from source.

My current favorite way to get the latest Emacs which has some additional stuff for the Mac is by using a build script, which builds Emacs from its source.

It’s called build-emacs-macos and is on Github. The instructions to use it are here in the README.

Permalink

October 12, 2022

emacs wsl

Emacs on Windows WSL2

So, it turns out that WSL2 is actually kind of neat, where it runs a Linux image at almost native speed, and also supports Wayland.

So, what’s the first thing you do in WSL2? Install Emacs of course!

Below is the script I use to install Emacs on an Ubuntu image.

To know what the latest stable version on master is, I look at this Github issue from Jim Myhrberg, who keeps track of those.

# Checkout Emacs
$ git clone git://git.sv.gnu.org/emacs.git

# Checkout latest stable version, see note above.
$ git checkout 8febda4

# Vanilla Emacs requirements
$ sudo apt install build-essential autoconf libgtk-3-dev libgnutls28-dev libtiff5-dev libgif-dev libjpeg-dev libpng-dev librsvg2-dev libxpm-dev libncurses-dev texinfo adwaita-icon-theme-full

# Native compilation requirements
$ sudo apt install libgccjit-11-dev

# Required for Native JSON
$ sudo apt install libjansson4 libjansson-dev

# Required for tree-sitter support
$ sudo apt install libtree-sitter-dev

$ cd emacs
$ export CC=/usr/bin/gcc-11 CXX=/usr/bin/gcc-11
$ ./autogen.sh
$ ./configure --with-pgtk --with-native-compilation --with-tree-sitter --with-json --without-pop
$ make -j$(nproc)
$ sudo make install

And, sometimes when you update the repository, it refuses to build. I usually fix that with running make bootstrap er make distclean.

Permalink

September 7, 2022

wsl

Syncthing on WSL2

I like to use Syncthing to sync files between accounts and machines, and now even between my Windows subsystem and Windows itself.

To run Syncthing on WSL I use this script:

#!/usr/bin/env bash
SERVICE="syncthing"
USER="petar"
PORT=2103
OPTS="
        --no-browser
        --home=/home/$USER/.config/syncthing
        --gui-address=http://127.0.0.1:$PORT
        --logfile=/home/$USER/.config/syncthing/syncthing.log
"

if ! pgrep -x "$SERVICE" >/dev/null
then
        daemonize /usr/bin/syncthing serve "$OPTS"
fi

Make sure to change your username and to make the port unique, if you run multiple Syncthings on your machine.

Permalink

September 5, 2022

rust

Rust Analyzer from Source

I’m developing on a FreeBSD machine and rust-analyzer is not available as a pre-build release.

Luckily, it’s easy to build rust-analyzer from source. Just clone the repository and run:

cargo xtask install --server

I’m on Emacs, so I only need the server, hence the --server argument. If you are using Visual Studio Code, don’t add it.

Permalink

June 10, 2022

lisp

How to install Quicklisp

Before starting, make sure you have installed SBCL. Then get the quicklisp file which enables you to bootstrap and install Quicklisp.

cd ~/downloads
curl -O http://beta.quicklisp.org/quicklisp.lisp
sbcl --load quicklisp.lisp

Now you are in the REPL and you can install Quicklisp. By default, it will be installed in ~/quicklisp, but I like to keep my home directory clean and install it in ~/.local/share/quicklisp. Thus the :path argument.

(quicklisp-quickstart:install :path "~/.local/share/quicklisp/")

I also prefer Quicklisp to always be available when I start SBCL, so I add it to my init file:

(ql:add-to-init-file)

That’s it! Happy Lisping!

Permalink

May 10, 2022

lisp asdf

Switch the current working directory in the REPL

If you start a REPL in the wrong directory, you can switch it by using UIOP.

UIOP is the portability layer of ASDF, supplying an abstraction on top of different implementations or OS’es.

For example, to change to a new project do:

(uiop:chdir "~/lisp/fangorn/")

Permalink

Make your own packages available to Quicklisp

In Lisp it’s quite common to have your own helper libraries, which are packaged as ASDF packages. To be able to use your libraries in other projects, you need to let ASDF know how to find them.

First create the directory ~/.config/common-lisp/source-registry.conf.d/

There create a file with any name of your choice but with the type conf, for instance 50-petar-lisp.conf.

In this file, add the following line to tell ASDF to recursively scan all the subdirectories under /home/petar/lisp/ for .asd files: (:tree "/home/petar/lisp/").

Permalink

May 9, 2022

lisp

Template for a new Common Lisp Project

To easily start a new Common Lisp project, I’m making use of the CL-Project from the famed Lisper Eitaro Fukamachi.

You can either install it through roswell with ros install cl-project or through the use of Quicklisp with (ql:quickload :cl-project).

The benefit of install it through Roswell is that you also get a binary.

To create a project through the REPL:

(cl-project:make-project #p"lisp/cl-sample/"
  :author "Eitaro Fukamachi"
  :email "[email protected]"
  :license "LLGPL"
  :depends-on '(:clack :cl-annot))

Or by using the binary:

make-project /home/user/common-lisp/sample \
    --name sample \
    --description "sample project." \
    --author "Your name" --license LLGPL \
    --depends-on alexandria split-sequence`

Permalink

April 14, 2022

rust

Cargo modules

Today I learned about the cargo-modules, a cargo plugin which shows you an overview of your crates modules.

I like to check the tree and my public interface with:

cargo modules generate tree --with-types

Permalink

January 2, 2022

command-line fish

November 15, 2021

lisp ai

From Lisp to Python, but why Peter?

Peter Norvig is a well-known educator on AI and used to be one of the key figures in the Lisp movement.

His books used to use Lisp in their exercises, but he switched to Python at some point. Today, I came across the Lex Fridman podcast, where he interviewed Peter Norvig and asked him why he made that change.

He gives a surprisingly simple answer at the 43-minute mark.

I was expecting deeper reasons, but his students were having difficulty grasping Lisp, and a questionnaire showed that Python mimicked the pseudocode from the book the most. That’s it.

In the short amount of time he had to educate them on AI, he could not spare the time to educate them on Lisp.

Coincidentally, I also came across this gem at smuglispweeny blog:

At ILC 2002 former Lisp giant now Python advocate Peter Norvig was for some reason allowed to give the keynote address like Martin Luther leading Easter Sunday mass at the Vatican and pitching Protestantism because in his talk Peter bravely repeated his claim that Python is a Lisp.

When he finished Peter took questions and to my surprise called first on the rumpled old guy who had wandered in just before the talk began and eased himself into a chair just across the aisle from me and a few rows up.

This guy had wild white hair and a scraggly white beard and looked hopelessly lost as if he had gotten separated from the tour group and wandered in mostly to rest his feet and just a little to see what we were all up to. My first thought was that he would be terribly disappointed by our bizarre topic and my second thought was that he would be about the right age, Stanford is just down the road, I think he is still at Stanford – could it be?

“Yes, John?” Peter said.

I won’t pretend to remember Lisp inventor John McCarthy’s exact words which is odd because there were only about ten but he simply asked if Python could gracefully manipulate Python code as data.

“No, John, it can’t,” said Peter and nothing more, graciously assenting to the professor’s critique, and McCarthy said no more though Peter waited a moment to see if he would and in the silence a thousand words were said.

Permalink

August 1, 2021

rust

Add aliases for your Rust project

I was looking at the Monkey language implementation done by Lauren Tan and noticed a .cargo/config.toml file in the project directory. In it was a convenient alias for her project:

dev = "watch -x check -x test"

This way, we can run cargo dev, which will run a check, which is not a build but catches a lot of errors quickly. The test is for those doing TDD because it will run the tests after every change. Convenient!

Permalink

August 1, 2021

reading

Changing working directory in tmux

I tend to keep a long-lived tmux session per project that I’m working on. When I want to start a new project though, it carries the old working directory with it. I keep forgetting what I need to do to type it, so hopefully this TIL makes me remember it for the next time.

To switch the current working directory, do a :attach -c <newdir>. And to do it it even more easily, I bound it in my tmux configuration to Meta-w:

bind M-w attach -c "#{pane_current_path}"

Permalink

SSH Tunnels

I often rely on SSH tunnels to forward remote ports locally. For example, to control my remote installation of Resilio Sync. There is a lot of flags you can set, but these are the flags that work best for me:

ssh -CqTnNf -L 8889:localhost:8888 [email protected]

In this example, I forward the remote localhost:8888 port to my local 8889. The flags do the following:

  • C: Compress the data.
  • q: Silent modus.
  • T: Disable pseudo-tty allocation.
  • n: Prevent reading stdin.
  • N: No remote commands, just forwarding.
  • f: Run in the background.
  • L: Specifies the forward.

Now, if you want to exit the tunnel, you could kill SSH pkill ssh, but this will kill all your SSH connections. I multiplex my connections and can run: ssh -O exit [email protected]. You would need this in your ~/ssh/.config to be able to do that as well:

Host *
  ControlMaster auto
  ControlPath ~/.ssh/sockets/%r@%h-%p

Permalink

Rust project templates

Rust never stops to amaze me with its command-line tools and today I found out about cargo-generate which enables you to setup a repository which will function as a project template for Rust.

As an example, to pull down the repository and start a new project, you would do:

cargo generate --git https://github.com/githubusername/mytemplate.git --name myproject

Permalink

Staying up-to-date with Rust

I have this tick where I continuously check that my tools are running the latest version. I guess you could call it a form of FOMO.

Luckily, Rust has me covered with two packages which check that for me.

cargo-outdated which check that your project dependenies are up-to-date. And cargo-update which checks that your Rust executables, like cargo-edit are running the latest version.

It will even update itself!

Permalink

June 9, 2021

life

Do not get to shame

The code I’m still ashamed of is an excellent read, reminding us of our responsibility as software developers.

It also reminded me of an experience we had with Bread & Pepper, the web agency we ran from 2009-2013. A government agency approached us to build an iPhone app where you could overlay your face on Einstein. We could have used the money but decided to be frank and tell them that the idea was ridiculous and a waste of public funds.

After a few weeks, we received the response that we were the only ones who told them the truth and picked us to continue the conversation on how to achieve their goals. We got rewarded for our honesty that day.

I tend to pay attention to the feeling of shame, its trying to tell me that I did something against my morals.

Reminder that shame is a strong indicator that you messed up. Be morally strong and avoid that feeling of shame.

Permalink

June 6, 2021

system-design

Tombstones for code

An interesting 5-minute talk called Isn’t That Code Dead? where David Schnepper talks about a technique called “Tombstones.”

The simple technique where you place a marker in your code which logs if it’s ever called. After a while, you check your logs and see what markers are untouched, giving you the confidence to remove that code.

Great way to find dead code in situations where the compiler can’t help you, for example interfaces exposed on the network.

Tombstones is a simple technique which can help you reduce the lines of code by eliminating dead code.

Permalink

May 24, 2021

rust

Rust tooling came a long way

I bought the Hands-on Rust and needed to setup my editor (Visual Studio Code) to start working on my 2D dungeon crawler. Choose Rust Analyzer and could not be more impressed with the experience.

Also impressed with some of the Cargo helper tools, like cargo-edit to manage dependencies, cargo-audit to check for vulnerabilities and cargo-expand to expand macro’s.

2021 is a great year to start learning Rust. Both the ecosystem and learning materials have reached a high level of maturity.

Permalink

May 23, 2021

management book

Ask Iwata

Ask Iwata was a quick read, a loosely connected series of short essays where I read a lot of things which resonated with me.

On my business card, I am a corporate president. In my mind, I am a game developer. But in my heart, I am a gamer.

As you move to management, you must never loose focus on the core values of your business. It’s your role to create an environment which inspires towards those goals and that empowers your team to do their best work.

Management frameworks and processes often miss this point, where they serve the manager instead of the team.

As a manager you can never loose touch with the process of creation. It’s your job to create an environment where that can thrive.

Permalink