Last active
December 8, 2025 16:14
-
-
Save iccir/92b344d27fec2f5e687c5752d03dfc2d to your computer and use it in GitHub Desktop.
Works around Sublime Text #6815 by manually sending selection to AppKit.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Attempts to work around Issue #6815 by manually sending | |
| # the selected text to the macOS Find Pasteboard | |
| # | |
| # https://github.com/sublimehq/sublime_text/issues/6815 | |
| from __future__ import annotations | |
| import ctypes | |
| import ctypes.util | |
| import sublime | |
| import sublime_plugin | |
| # If True, loads AppKit via ctypes and uses the system NSPasteboard API | |
| # If False, attempts to slurp find string with Sublime API | |
| ShouldSlurpWithAppKit = True | |
| class FindPasteboard(): | |
| def __init__(self): | |
| AppKit = ctypes.cdll.LoadLibrary(ctypes.util.find_library("AppKit")) | |
| objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library("objc")) | |
| objc.objc_getClass.restype = ctypes.c_void_p | |
| objc.sel_registerName.restype = ctypes.c_void_p | |
| NSAutoreleasePool = objc.objc_getClass(b'NSAutoreleasePool') | |
| NSPasteboard = objc.objc_getClass(b'NSPasteboard') | |
| NSString = objc.objc_getClass(b'NSString') | |
| NSPasteboardNameFind = ctypes.c_void_p.in_dll(AppKit, "NSPasteboardNameFind") | |
| NSPasteboardTypeString = ctypes.c_void_p.in_dll(AppKit, "NSPasteboardTypeString") | |
| def m_(object, selector, *args, restype = ctypes.c_void_p): | |
| argtypes = [ ctypes.c_void_p, ctypes.c_void_p ] | |
| for arg in args: | |
| argtypes.append(ctypes.c_void_p) | |
| objc.objc_msgSend.restype = restype | |
| objc.objc_msgSend.argtypes = argtypes | |
| return objc.objc_msgSend(object, objc.sel_registerName(selector), *args) | |
| def read() -> str: | |
| pool = m_(m_(NSAutoreleasePool, b"alloc"), b"init") | |
| pasteboard = m_(NSPasteboard, b"pasteboardWithName:", NSPasteboardNameFind) | |
| ns_string = m_(pasteboard, b"stringForType:", NSPasteboardTypeString) | |
| b = m_(ns_string, b"UTF8String", restype=ctypes.c_char_p) | |
| string = b.decode() if b else None | |
| m_(pool, b"release") | |
| return string | |
| def write(in_string: Optional[str]) -> None: | |
| pool = m_(m_(NSAutoreleasePool, b"alloc"), b"init") | |
| pasteboard = m_(NSPasteboard, b"pasteboardWithName:", NSPasteboardNameFind) | |
| m_(pasteboard, b"clearContents") | |
| if in_string: | |
| ns_string = m_(NSString, b'stringWithUTF8String:', in_string.encode()) | |
| m_(pasteboard, b"setString:forType:", ns_string, NSPasteboardTypeString) | |
| m_(pool, b"release") | |
| self.__read = read | |
| self.__write = write | |
| def read(self) -> str: | |
| return self.__read() | |
| def write(self, str: str) -> None: | |
| self.__write(str) | |
| find_pasteboard = None | |
| def do_slurp_with_sublime_api(in_view: sublime.View, string: str) -> None: | |
| # From testing, the "slurp_find_string" command only works when a | |
| # sublime.View object has a None element() | |
| # To work around this, we need to create a new View, append the selection | |
| # string to it, and then run the "slurp_find_string" command | |
| tmp_view = in_view.window().new_file(flags=sublime.NewFileFlags.TRANSIENT) | |
| tmp_view.set_scratch(True) | |
| tmp_view.run_command("append", { "characters": string }) | |
| tmp_view.run_command("select_all") | |
| tmp_view.run_command("slurp_find_string") | |
| tmp_view.close() | |
| # Creating the temporary View shifted input focus, re-focus to the original | |
| in_view.window().focus_view(in_view) | |
| def do_slurp_with_appkit(string: str) -> None: | |
| global find_pasteboard | |
| if not find_pasteboard: | |
| find_pasteboard = FindPasteboard() | |
| find_pasteboard.write(string) | |
| class FixedSlurpFindString(sublime_plugin.TextCommand): | |
| def run(self, edit): | |
| view = self.view | |
| # Only use our workaround if the currently focused input view has an element() | |
| if view.element(): | |
| selection = view.sel() | |
| if len(selection) == 0: return | |
| string = view.substr(selection[0]) | |
| if ShouldSlurpWithAppKit: | |
| do_slurp_with_appkit(string) | |
| else: | |
| do_slurp_with_sublime_api(view, string) | |
| # Else, go ahead and use the built-in "slurp_find_string" | |
| else: | |
| view.run_command("slurp_find_string") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment