patx/twig
version 2. clean up key bindings
Commit dc74faf · patx · 2026-06-14T18:42:22-04:00
Comments
No comments yet.
Diff
diff --git a/Makefile b/Makefile
index e0af79a..439c03a 100644
--- a/Makefile
+++ b/Makefile
@@ -5,7 +5,7 @@ APPDIR ?= $(DATADIR)/applications
ICONDIR ?= $(DATADIR)/icons/hicolor
MANDIR ?= $(DATADIR)/man
PACKAGE ?= twig
-VERSION ?= 0.1.0
+VERSION ?= 0.1.1
DEB_ARCH ?= all
DEB_BUILD_DIR ?= build/deb
DEB_ROOT ?= $(DEB_BUILD_DIR)/$(PACKAGE)
diff --git a/README.md b/README.md
index a6e1824..1836367 100644
--- a/README.md
+++ b/README.md
@@ -64,18 +64,19 @@ Twig intentionally has no toolbar or menu bar. Use these keyboard shortcuts:
| `Ctrl+P` | Print |
| `Ctrl+Q` | Quit Twig, prompting for unsaved files |
| `Ctrl+Z` | Undo |
-| `Ctrl+Shift+Z` or `Ctrl+Y` | Redo |
+| `Ctrl+Shift+Z` | Redo |
| `Ctrl+X` | Cut |
| `Ctrl+C` | Copy |
| `Ctrl+V` | Paste |
| `Ctrl+A` | Select all |
+| `Ctrl+D` | Delete the selected lines or current line |
| `Ctrl+F` | Open or focus Find and Replace |
-| `Ctrl+G` or `F3` | Find next match |
-| `Ctrl+Shift+G` or `Shift+F3` | Find previous match |
-| `Ctrl+H` or `Ctrl+R` | Open Find and Replace with the replace field focused |
+| `Ctrl+G` | Find next match |
+| `Ctrl+Shift+G` | Find previous match |
+| `Ctrl+R` | Open Find and Replace with the replace field focused |
| `Ctrl+J` | Jump to line |
-| `Ctrl+Shift++` | Increase editor font size |
-| `Ctrl+Shift+-` | Decrease editor font size |
+| `Ctrl++` | Increase editor font size |
+| `Ctrl+-` | Decrease editor font size |
| `Tab` with selected lines | Indent selected lines with spaces |
| `Shift+Tab` with selected lines | Unindent selected lines |
| `Enter` in Find | Find next match |
diff --git a/dist/twig_0.1.0_all.deb b/dist/twig_0.1.0_all.deb
deleted file mode 100644
index 8fe1d3e..0000000
Binary files a/dist/twig_0.1.0_all.deb and /dev/null differ
diff --git a/dist/twig_0.1.1_all.deb b/dist/twig_0.1.1_all.deb
new file mode 100644
index 0000000..94934bd
Binary files /dev/null and b/dist/twig_0.1.1_all.deb differ
diff --git a/docs/index.html b/docs/index.html
index 4567507..bbe04b5 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -60,7 +60,7 @@
<img src="https://gitman.io/patx/twig/raw/docs/logo.png" alt="Twig logo">
</div>
<p><strong>Twig is an ultra-minimal GTK code editor</strong> for Debian/Ubuntu based Linux distros. No toolbar or menu bar, just code. <a href="https://gitman.io/patx/twig/raw/docs/screenshot.png" target="_blank">View screenshot</a>.</p>
-<p>Download the <a href="https://gitman.io/patx/twig/raw/dist/twig_0.1.0_all.deb" target="_blank">.deb</a>. You can also install Twig from <a href="https://gitman.io/patx/twig" target="_blank">source</a>.</p>
+<p>Download the <a href="https://gitman.io/patx/twig/raw/dist/twig_0.1.1_all.deb" target="_blank">.deb</a>. You can also install Twig from <a href="https://gitman.io/patx/twig" target="_blank">source</a>.</p>
<h2>Features</h2>
<ul>
@@ -78,19 +78,18 @@
<table>
<tr><td><code>Ctrl+N</code> / <code>Ctrl+T</code></td><td>New window</td></tr>
<tr><td><code>Ctrl+O</code></td><td>Open file(s)</td></tr>
- <tr><td><code>Ctrl+S</code></td><td>Save</td></tr>
- <tr><td><code>Ctrl+Shift+S</code></td><td>Save as</td></tr>
+ <tr><td><code>Ctrl+S</code> / <code>Ctrl+Shift+S</code></td><td>Save / Save as</td></tr>
<tr><td><code>Ctrl+W</code></td><td>Close window</td></tr>
<tr><td><code>Ctrl+P</code></td><td>Print</td></tr>
<tr><td><code>Ctrl+Q</code></td><td>Quit</td></tr>
- <tr><td><code>Ctrl+Z</code></td><td>Undo</td></tr>
- <tr><td><code>Ctrl+Shift+Z</code> / <code>Ctrl+Y</code></td><td>Redo</td></tr>
+ <tr><td><code>Ctrl+Z</code> / <code>Ctrl+Shift+Z</code></td><td>Undo / Redo</td></tr>
<tr><td><code>Ctrl+X</code> / <code>Ctrl+C</code> / <code>Ctrl+V</code></td><td>Cut / Copy / Paste</td></tr>
<tr><td><code>Ctrl+A</code></td><td>Select all</td></tr>
+ <tr><td><code>Ctrl+D</code></td><td>Delete line(s)</td></tr>
<tr><td><code>Ctrl+F / Ctrl+R</code></td><td>Find / Replace</td></tr>
<tr><td><code>Ctrl+G</code> / <code>Ctrl+Shift+G</code></td><td>Find next / previous</td></tr>
<tr><td><code>Ctrl+J</code></td><td>Jump to line</td></tr>
- <tr><td><code>Ctrl+Shift++</code> / <code>Ctrl+Shift+-</code></td><td>Increase / decrease editor font size</td></tr>
+ <tr><td><code>Ctrl++</code> / <code>Ctrl+-</code></td><td>Increase / decrease editor font size</td></tr>
<tr><td><code>Tab</code> / <code>Shift+Tab</code></td><td>Indent / Unindent</td></tr>
</table>
diff --git a/docs/twig.1 b/docs/twig.1
index 6f16875..c2c7d6b 100644
--- a/docs/twig.1
+++ b/docs/twig.1
@@ -1,4 +1,4 @@
-.TH TWIG 1 "June 2026" "Twig 0.1.0" "User Commands"
+.TH TWIG 1 "June 2026" "Twig 0.1.1" "User Commands"
.SH NAME
twig \- lightweight GTK code editor
.SH SYNOPSIS
@@ -23,6 +23,9 @@ Save the current file as a new path.
.B Ctrl+F
Open or focus find and replace.
.TP
+.B Ctrl+D
+Delete the selected lines or current line.
+.TP
.B Ctrl+Q
Quit Twig, prompting for unsaved files.
.SH AUTHOR
diff --git a/tests/test_helpers.py b/tests/test_helpers.py
index 084f1e7..889686c 100644
--- a/tests/test_helpers.py
+++ b/tests/test_helpers.py
@@ -10,14 +10,19 @@ spec.loader.exec_module(twig)
class HelperTests(unittest.TestCase):
- def test_detect_language_from_filename(self):
- self.assertEqual(twig.detect_language("example.py"), "python")
- self.assertEqual(twig.detect_language("index.html"), "html")
- self.assertEqual(twig.detect_language("style.css"), "css")
- self.assertEqual(twig.detect_language("script.js"), "javascript")
- self.assertEqual(twig.detect_language("README.md"), "markdown")
- self.assertEqual(twig.detect_language("Makefile"), "config")
- self.assertEqual(twig.detect_language("notes.txt"), "plain")
+ def test_gtksourceview_guesses_language_from_filename(self):
+ manager = twig.GtkSource.LanguageManager.get_default()
+
+ def language_id(filename):
+ language = manager.guess_language(filename, None)
+ return language.get_id() if language else None
+
+ self.assertEqual(language_id("example.py"), "python")
+ self.assertEqual(language_id("index.html"), "html")
+ self.assertEqual(language_id("style.css"), "css")
+ self.assertEqual(language_id("script.js"), "js")
+ self.assertEqual(language_id("README.md"), "markdown")
+ self.assertIsNone(language_id("notes.txt"))
def test_clamp_font_size(self):
self.assertEqual(twig.clamp_font_size(1), twig.MIN_EDITOR_FONT_SIZE)
diff --git a/twig.py b/twig.py
index 5ff64c3..7349bcc 100755
--- a/twig.py
+++ b/twig.py
@@ -2,6 +2,7 @@
"""Twig: a tiny GTK code editor for lightweight Linux desktops."""
import os
+import shutil
import sys
from pathlib import Path
@@ -39,6 +40,23 @@ EDITOR_FONT_STEP = 1
HEADERBAR_DESKTOPS = {"gnome", "pantheon"}
+def clamp_font_size(size):
+ return min(MAX_EDITOR_FONT_SIZE, max(MIN_EDITOR_FONT_SIZE, size))
+
+
+def line_count_for_text(text):
+ return text.count("\n") + 1
+
+
+def select_print_command(is_available=None):
+ if is_available is None:
+ is_available = lambda command: shutil.which(command) is not None
+ for command in ("lpr", "lp"):
+ if is_available(command):
+ return command
+ return None
+
+
def should_use_headerbar():
desktop_names = (
os.environ.get("XDG_CURRENT_DESKTOP", ""),
@@ -189,6 +207,7 @@ class TwigWindow(Gtk.ApplicationWindow):
"copy": self.on_copy,
"paste": self.on_paste,
"select-all": self.on_select_all,
+ "delete-line": self.on_delete_line,
}
for name, callback in actions.items():
action = Gio.SimpleAction.new(name, None)
@@ -260,10 +279,7 @@ class TwigWindow(Gtk.ApplicationWindow):
self.view.queue_draw()
def _change_editor_font_size(self, delta):
- new_size = min(
- MAX_EDITOR_FONT_SIZE,
- max(MIN_EDITOR_FONT_SIZE, self.editor_font_size + delta),
- )
+ new_size = clamp_font_size(self.editor_font_size + delta)
if new_size != self.editor_font_size:
self.editor_font_size = new_size
self._apply_editor_font_size()
@@ -489,6 +505,32 @@ class TwigWindow(Gtk.ApplicationWindow):
self.buffer.end_user_action()
return True
+ def delete_selected_lines(self):
+ if self.buffer.get_has_selection():
+ start, last_line = self.selected_line_bounds()
+ else:
+ cursor = self.buffer.get_iter_at_mark(self.buffer.get_insert())
+ start = self.buffer.get_iter_at_line(cursor.get_line())
+ last_line = cursor.get_line()
+
+ if last_line + 1 < self.buffer.get_line_count():
+ end = self.buffer.get_iter_at_line(last_line + 1)
+ else:
+ end = self.buffer.get_end_iter()
+ if start.get_line() > 0:
+ start.backward_char()
+
+ cursor_offset = start.get_offset()
+ self.buffer.begin_user_action()
+ self.buffer.delete(start, end)
+ self.buffer.end_user_action()
+
+ target_offset = min(cursor_offset, self.buffer.get_end_iter().get_offset())
+ target = self.buffer.get_iter_at_offset(target_offset)
+ self.buffer.place_cursor(target)
+ self.view.scroll_to_iter(target, 0.15, False, 0.0, 0.0)
+ return True
+
def jump_to_line(self, line_number):
line_count = self.buffer.get_line_count()
line_index = max(0, min(line_number - 1, line_count - 1))
@@ -625,23 +667,24 @@ class TwigWindow(Gtk.ApplicationWindow):
def on_select_all(self, *_args):
self.buffer.select_range(self.buffer.get_start_iter(), self.buffer.get_end_iter())
+ def on_delete_line(self, *_args):
+ self.delete_selected_lines()
+
def _is_editor_font_shortcut(self, event):
state = event.state & Gtk.accelerator_get_default_mod_mask()
- required = Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK
- if (state & required) != required:
+ if not state & Gdk.ModifierType.CONTROL_MASK:
return False
return event.keyval in (
Gdk.KEY_plus,
Gdk.KEY_equal,
Gdk.KEY_KP_Add,
Gdk.KEY_minus,
- Gdk.KEY_underscore,
Gdk.KEY_KP_Subtract,
)
def on_key_press(self, _view, event):
if self._is_editor_font_shortcut(event):
- if event.keyval in (Gdk.KEY_minus, Gdk.KEY_underscore, Gdk.KEY_KP_Subtract):
+ if event.keyval in (Gdk.KEY_minus, Gdk.KEY_KP_Subtract):
return self._change_editor_font_size(-EDITOR_FONT_STEP)
return self._change_editor_font_size(EDITOR_FONT_STEP)
if event.keyval == Gdk.KEY_Tab and self.buffer.get_has_selection():
@@ -785,16 +828,17 @@ class TwigApp(Gtk.Application):
"app.quit": ["<Primary>q"],
"win.print": ["<Primary>p"],
"win.find": ["<Primary>f"],
- "win.replace": ["<Primary>h", "<Primary>r"],
- "win.find-next": ["F3", "<Primary>g"],
- "win.find-prev": ["<Shift>F3", "<Primary><Shift>g"],
+ "win.replace": ["<Primary>r"],
+ "win.find-next": ["<Primary>g"],
+ "win.find-prev": ["<Primary><Shift>g"],
"win.jump-to": ["<Primary>j"],
"win.undo": ["<Primary>z"],
- "win.redo": ["<Primary>y", "<Primary><Shift>z"],
+ "win.redo": ["<Primary><Shift>z"],
"win.cut": ["<Primary>x"],
"win.copy": ["<Primary>c"],
"win.paste": ["<Primary>v"],
"win.select-all": ["<Primary>a"],
+ "win.delete-line": ["<Primary>d"],
}
for action, accels in shortcuts.items():
self.set_accels_for_action(action, accels)