Skip to content

Instantly share code, notes, and snippets.

@flavio-fernandes
Created August 3, 2026 19:15
Show Gist options
  • Select an option

  • Save flavio-fernandes/c065e8564361dd4fe027b4b9d24726bf to your computer and use it in GitHub Desktop.

Select an option

Save flavio-fernandes/c065e8564361dd4fe027b4b9d24726bf to your computer and use it in GitHub Desktop.
JED Emacs-style config with repeated C-w incremental search
% ~/.jedrc
%
% Emacs-like JED configuration.
% Load JED's built-in Emacs keybindings first.
() = evalfile ("emacs");
% Load the locally patched incremental search implementation.
% It adds Emacs-like repeated C-w behavior while C-s/C-r is active.
() = evalfile (strcat (getenv ("HOME"), "/.jed/isearch-emacs-word.sl"));
% C-x g prompts for a line number.
setkey ("goto_line_cmd", "^Xg");
% Show line and column numbers in the status line.
LINENUMBERS = 2;
% Do not create backup files ending in ~.
No_Backups = 1;
% Four-space indentation, with spaces instead of hard tabs.
TAB_DEFAULT = 4;
USE_TABS_DEFAULT = 0;
C_INDENT = 4;

Lightweight Emacs-style editing with JED

This configuration provides:

  • JED's built-in Emacs keybindings
  • C-x g for goto-line
  • line and column numbers in the status line
  • four-space indentation with no hard tabs
  • no ~ backup files
  • emacs as an alias for jed
  • Emacs-like repeated C-w while incremental search is active

Incremental search behavior

Place point on a word and press:

C-s C-w

That yanks the current word into the search.

Repeated C-w presses extend the search using the text at the end of the current match:

C-s C-w C-w C-w

For text such as alpha beta, those presses collect:

  1. alpha
  2. the space
  3. beta

During the search:

  • C-s moves to the next match
  • C-r moves to the previous match
  • Backspace removes one search character
  • Enter accepts the match
  • C-g aborts and returns to the starting point

JED's normal fsearch and bsearch do not match across lines, so this patch intentionally refuses to yank a newline with C-w.

Fresh Debian, Ubuntu, or Raspberry Pi OS system

Install JED and GitHub CLI:

sudo apt update
sudo apt install -y jed gh

Clone this gist and run the installer:

workdir=$(mktemp -d)
gh gist clone GIST_ID_OR_URL "$workdir"
bash "$workdir/install.sh"
rm -rf -- "$workdir"
source ~/.bash_aliases

The installer is idempotent. It:

  1. Locates the distro's current isearch.sl from jed-common.
  2. Copies it into ~/.jed/isearch-emacs-word.sl.
  3. Applies the small repeated-C-w patch to that local copy.
  4. Validates the S-Lang file with JED batch mode.
  5. Backs up an existing ~/.jedrc before installing the new one.
  6. Adds alias emacs='jed' to ~/.bash_aliases if it is missing.

After a JED package upgrade

Re-run the same installer. It always rebuilds the patched file from the currently installed distro copy of isearch.sl.

Files

  • .jedrc -- editor settings and keybindings
  • install.sh -- guarded, repeatable installer

The incremental-search patch is derived at install time from JED's installed isearch.sl, rather than distributing a permanently forked copy.

#!/usr/bin/env bash
set -euo pipefail
readonly SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
readonly JED_DIR=${HOME}/.jed
readonly TARGET_ISEARCH=${JED_DIR}/isearch-emacs-word.sl
readonly TARGET_JEDRC=${HOME}/.jedrc
readonly ALIAS_FILE=${HOME}/.bash_aliases
readonly MARKER='Emacs-like repeated C-w support'
fail() {
printf 'ERROR: %s\n' "$*" >&2
exit 1
}
command -v jed >/dev/null 2>&1 || fail 'jed is not installed. Run: sudo apt update && sudo apt install -y jed'
command -v python3 >/dev/null 2>&1 || fail 'python3 is required but was not found.'
command -v dpkg >/dev/null 2>&1 || fail 'This installer expects a Debian/Ubuntu/Raspberry Pi OS system with dpkg.'
[[ -f ${SCRIPT_DIR}/.jedrc ]] || fail "Missing ${SCRIPT_DIR}/.jedrc"
upstream_isearch=$(dpkg -L jed-common 2>/dev/null | awk '/\/isearch\.sl$/ { print; exit }')
[[ -n ${upstream_isearch} ]] || fail 'Could not locate isearch.sl from the jed-common package.'
[[ -r ${upstream_isearch} ]] || fail "Cannot read ${upstream_isearch}"
mkdir -p -- "$JED_DIR"
# Always rebuild from the currently installed JED source. This makes package
# upgrades safe and keeps this patch tied to the local distro version.
cp -- "$upstream_isearch" "$TARGET_ISEARCH"
python3 - "$TARGET_ISEARCH" <<'PY'
from __future__ import annotations
import sys
from pathlib import Path
path = Path(sys.argv[1])
text = path.read_text(encoding="utf-8")
marker = "Emacs-like repeated C-w support"
if marker in text:
raise SystemExit(0)
helper_anchor = "\ndefine isearch_dir (dir)\n"
case_anchor = "\n#ifdef IBMPC_SYSTEM\n"
if helper_anchor not in text:
raise SystemExit("Unable to patch JED: isearch_dir anchor was not found")
if case_anchor not in text:
raise SystemExit("Unable to patch JED: control-key switch anchor was not found")
helper = r'''
% Emacs-like repeated C-w support
%
% During incremental search, C-w appends either:
% * the remaining word characters at the end of the current match, or
% * one separator character, such as a space or punctuation.
%
% Therefore C-s C-w C-w C-w can collect "word", then its separator,
% then the following word, closely matching GNU Emacs behavior.
private define isearch_yank_word_or_char (str)
{
variable text = "";
variable start_point;
if (Last_Search_Failed)
return text;
push_spot ();
% JED leaves point at the beginning of the current match.
% Move to the end of the text already present in the search string.
if (strlen (str) and looking_at (str))
() = right (strlen (str));
% JED's plain fsearch/bsearch cannot match across lines. Do not yank a
% newline, because doing so would create a search string that cannot match.
if (eolp () or eobp ())
{
pop_spot ();
return text;
}
push_mark ();
start_point = _get_point ();
% If point is on a word character, collect through the end of that word.
skip_word_chars ();
% Otherwise collect exactly one separator character. Repeated C-w presses
% can therefore include spaces and punctuation before collecting a word.
if (_get_point () == start_point)
() = right (1);
text = bufsubstr ();
pop_mark (0);
pop_spot ();
return text;
}
'''
case = r'''
{ case 23 : % ^W
variable yanked = isearch_yank_word_or_char (str);
variable n;
ifnot (strlen (yanked))
{
beep ();
continue;
}
% Keep deletion/backtracking granular. One DEL removes one
% character, even when a C-w press appended a complete word.
n = strlen (yanked);
while (n > 0)
{
push_position (1);
n--;
}
str += yanked;
Last_Search_Failed = 0;
% Treat this as printable input for the reverse-search shortcut
% test below.
c = ' ';
}
'''
text = text.replace(helper_anchor, helper + helper_anchor, 1)
text = text.replace(case_anchor, case + case_anchor, 1)
path.write_text(text, encoding="utf-8")
PY
# Validate that JED can parse the patched search implementation before touching
# the user's active startup file.
if ! jed -batch -n -l "$TARGET_ISEARCH" >/dev/null 2>&1; then
rm -f -- "$TARGET_ISEARCH"
fail 'JED could not parse the patched isearch file. The active ~/.jedrc was not changed.'
fi
if [[ -e $TARGET_JEDRC ]]; then
backup=${TARGET_JEDRC}.bak.$(date +%Y%m%d-%H%M%S)
cp -a -- "$TARGET_JEDRC" "$backup"
printf 'Backed up existing config to %s\n' "$backup"
fi
install -m 0644 -- "$SCRIPT_DIR/.jedrc" "$TARGET_JEDRC"
mkdir -p -- "$(dirname -- "$ALIAS_FILE")"
touch -- "$ALIAS_FILE"
if ! grep -Fqx "alias emacs='jed'" "$ALIAS_FILE"; then
printf "\nalias emacs='jed'\n" >> "$ALIAS_FILE"
fi
printf '\nInstalled:\n'
printf ' %s\n' "$TARGET_JEDRC"
printf ' %s\n' "$TARGET_ISEARCH"
printf ' %s\n' "$ALIAS_FILE"
printf '\nRun this in the current shell:\n'
printf ' source %q\n' "$ALIAS_FILE"
printf '\nThen start JED with either: jed FILE or emacs FILE\n'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment