Files
dualfroz 4ae3a496af refactor: extract the shared foundation into an installable library
The single file extension carried 260 lines of infrastructure under its
four Nemo providers: the config file with live reload, the git query
layer, the TTL cache with .git watching, and repository detection. Any
second extension wanting repository awareness had to repeat all of it.

Move that foundation into a nemoRepoCore package with a deliberate plugin
API rather than whatever the providers happened to leave behind:

  * Config gains layered defaults, so a plugin registers its own keys and
    an empty config file is still a working one
  * RepoInfo gains a root, a named last commit tuple and a summary string
  * RepoCache gains refresh, invalidate and an invalidation callback
  * discovery separates the cheap root check used on every drawn folder
    from the upward walk used by on demand work such as menus
  * nemoFiles collapses the uri scheme and directory guards every
    provider was repeating
  * launchers learn the per emulator flag for running a command
  * version negotiation lets a plugin state the core it was built for

The config file moves to nemo-repo-core.json and is seeded from the old
nemo-repo-emblems.json when one exists, so no key changes name and
nothing needs editing by hand.
2026-08-05 20:06:41 +02:00

107 lines
3.0 KiB
Python

"""Starting a terminal or an editor from a Nemo menu item.
Terminal emulators disagree about the flag that means "run this command",
so the mapping below is explicit rather than assuming one form works
everywhere. Unknown emulators fall back to -e, which is the oldest and most
widely accepted spelling.
"""
import os
import shutil
import subprocess
from gi.repository import GLib
from .Config import coreConfig
from .logs import pluginLogger
logger = pluginLogger("launchers")
terminalCandidates = [
"gnome-terminal",
"x-terminal-emulator",
"tilix",
"konsole",
"xfce4-terminal",
"mate-terminal",
"kitty",
"alacritty",
"xterm",
]
terminalCommandFlags = {
"gnome-terminal": ["--"],
"mate-terminal": ["--"],
"tilix": ["-e"],
"konsole": ["-e"],
"xfce4-terminal": ["-x"],
"kitty": [],
"alacritty": ["-e"],
"xterm": ["-e"],
"x-terminal-emulator": ["-e"],
}
fallbackCommandFlags = ["-e"]
def resolveTerminal():
"""The terminal command to use: the configured one, else the first found."""
configured = coreConfig.terminalCommand
if configured and shutil.which(configured.split(" ")[0]):
return configured
for candidate in terminalCandidates:
if shutil.which(candidate):
return candidate
return None
def _commandFlagsFor(terminal):
return terminalCommandFlags.get(os.path.basename(terminal), fallbackCommandFlags)
def launchTerminal(path, shellCommand=None):
"""Open a terminal in `path`, optionally running `shellCommand` first.
The shell stays open afterwards so the user can read the output of
whatever was run and keep working in the same directory.
"""
terminal = resolveTerminal()
if not terminal:
logger.warning("No terminal emulator found; tried %s",
", ".join(terminalCandidates))
return False
parts = terminal.split(" ")
try:
if shellCommand:
script = "cd %s && %s; exec \"${SHELL:-bash}\"" % (
GLib.shell_quote(path), shellCommand)
arguments = parts + _commandFlagsFor(parts[0]) + ["bash", "-c", script]
else:
arguments = parts
subprocess.Popen(arguments, cwd=path)
except OSError as error:
logger.warning("Could not start %s: %s", terminal, error)
return False
return True
def launchEditor(path):
"""Open `path` in the configured editor command."""
editor = coreConfig.editorCommand
parts = editor.split(" ")
if not shutil.which(parts[0]):
logger.warning("Editor command %r is not in PATH", editor)
return False
try:
subprocess.Popen(parts + [path])
except OSError as error:
logger.warning("Could not start editor %r: %s", editor, error)
return False
return True
def editorAvailable():
"""True when the configured editor command can actually be started."""
return shutil.which(coreConfig.editorCommand.split(" ")[0]) is not None