Is it possible to add a global X11 binding via/to Sublime Text 3?

163 Views Asked by At

I would want to add a global X11 binding, preferably via ST3 configuration, that when activated, would:

  • move the focus to a ST3 window and view,
  • this would also involve switching to the right virtual desktop,
  • invoke a sublime command (a text command or other).

Is this possible?

I'm doing some Xlib / EWMH coding and I can do the activation and desktop switching "manually". All I need is a global X11 binding that would invoke a Sublime Text command. Is such binding possible via Sublime configuration? If not, then how to accomplish the above?

1

There are 1 best solutions below

0
On

I'm not sure exactly what you mean by a global X11 binding but I'd say what you want to achieve is possible with Xlib and EWMH in conjunction with a Sublime Text plugin to focus the required view and invoke the ST command.

Below is a fully working basic mock up of the required logic which uses a Bash script to focus the window and switch virtual desktops and then to call a Sublime Text plugin which changes the view and runs the required command.

The script uses wmctrl a useful utility which issues commands to EWMH/NetWM compatible X Window Managers.

Save this file as a Bash script and give it executable permission:

#!/bin/bash

# EWMH WM_CLASS for Sublime Text 3: "sublime_text.Sublime_text"
#
# EWMH WM_NAME examples: "~/Path/FileName.ext (Project Name) - Sublime Text"
#                        "~/Path/No Project.ext - Sublime Text"
#                        "untitled - Sublime Text"
#                        "~/Path/FileName.ext • (Project) - Sublime Text"
#
# Note: The project file name without the extension will be shown in brackets
# if there is a project file. '•' (see last example above) will be added if
# the file is unsaved and the suffix " - Sublime Text" is always added.

# Example: "~/Programming/Projects/MyProject.sublime-project" --> "MyProject"
target_project="MyProject"

# Example "wmctrl -lx" line:
# 0x02400003 0 sublime_text.Sublime_text host ~/File.ext (MyProject) - Sublime Text
# WinID   Desktop      WM_CLASS        hostname             WM_NAME
target_window=$(wmctrl -lx | grep ".*sublime_text.Sublime_text.*($target_project).*")
if [ "$target_window" = "" ]; then
    exit
fi

target_window_id=$(echo "$target_window" | grep -Eo '0x[0-9a-f]+')
if [ "$target_window_id" = "" ]; then
    exit
fi

# Switch focus to the window, wmctrl will switch virtual desktops if needed.
wmctrl -ia "$target_window_id"

# Call the Sublime Text window command:
# [This assumes Sublime Text is already running.]
subl --background --command "focus_view_invoke_cmd"

Save this file somewhere in the ST3 config Packages directory tree with a .py extension. e.g. ~/.config/sublime-text-3/Packages/User/FocusViewInvokeCommand.py:

import os.path
import sublime
import sublime_plugin

# Command created by ST for this class: focus_view_invoke_cmd
# How to set or find the command name of a Sublime Text plugin
# see this answer: https://stackoverflow.com/a/63979147/2102457

class FocusViewInvokeCmdCommand(sublime_plugin.WindowCommand):

    def run(self):

        # view_name_to_match is a file or unsaved buffer
        # name that the focus should be switched to. e.g.
        view_name_to_match = "MyToDoList"

        for view in self.window.views():
            if view_name_to_match in self.view_name(view):
                self.window.focus_view(view)
                self.run_command(view)
                break


    def view_name(self, view):

        # Return the file name (not full path) if the buffer is saved.
        if view.file_name():
            path = view.file_name()
            return os.path.basename(path)

        # Return the buffer name; i.e. the name the user has assigned,
        # or the buffer's 1st line auto. assigned by ST, or "untitled".
        else:
            # "untitled" is the default displayed name shown by ST
            # for all buffers which are both unsaved and unnamed,
            # but view.name() returns "" for "untitled" buffers.
            return view.name() if view.name() else "untitled"


    def run_command(self, view):

        command_to_run = "insert"
        command_args = {"characters": "This is a test! :)"}

        # In each of the run_command() methods the
        # command_args parameter may be omitted.

        # Use to run a *text command* on the view.
        view.run_command(command_to_run, command_args)

        # Use to run a *window command*.
        # self.window.run_command(command_to_run, command_args)

        # Use to run an *application command*.
        # sublime.run_command(command_to_run, command_args)