diff --git a/README.md b/README.md index bcc3411..d6e3556 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,17 @@ To customize these, hit shift+cmd+p to open the Command Palette, and - **delimiters** - characters at which auto-expansion of selected path stops, e.g. `` \t\n\r\"'`,*<>[](){}`` - the default settings are Markdown friendly +- **delimiters_scoped** + - Same as delimiters, but for a specific scope, uses Sublime's [score_selector API](https://www.sublimetext.com/docs/api_reference.html#sublime.score_selector) to test whether a given key matches the scope of the current selection. The scope with the maximum match score wins. + - A list of `scope`/`scope_match_threshold`/`delimiters` dictionaries. + - **scope_match_threshold** Minimum score to consider a comparison a match (0 no match; >0 match, higher is better), e.g., for `text.html.markdown markup.list.unnumbered.markdown meta.paragraph.list.markdown` + - 0 `paragraph` + - 2 `text` `text.` + - 4 `text.html` + - 6 `text.html.markdown` + - 256 `meta.paragraph` +- **scope_stop** + - A list of scopes to stop path expansion at, e.g., a valid path symbol `//` when it's scoped as a `punctuation.definition.comment` - **trailing_delimiters** - if any of these characters are seen at the end of a URL, they are recursively removed; for file and folder paths, URLs **with and without** trailing delimiters are tried; default is `;.:` - **web_browser** @@ -145,9 +156,13 @@ To customize these, hit shift+cmd+p to open the Command Palette, and - the path to your web browser executable for opening web URLs - this setting overrides the default web browser and the **web_browser** setting - [read the top answer here](https://stackoverflow.com/questions/22445217/python-webbrowser-open-to-open-chrome-browser), or look in settings for examples +- **enable_web_search** + - if selection is not recognized as a file/folder/URL, pass it to web searches - **web_searchers** - if your selection isn't a file, a folder, or a URL, you can choose to pass it to a web searcher, which is just a URL that searches for the selected text - example: `{ "label": "google search", "url": "http://google.com/search?q=", "encoding": "utf-8" }` +- **live_edit** + - if selection is not recognized as a file/folder/url, show a panel to edit it - **aliases** - first transform applied to URL, a dict with keys and values; replace each **key** in URL with corresponding **value** - example: `{ "{{BASE_PATH}}": "src/base" }` @@ -160,9 +175,13 @@ To customize these, hit shift+cmd+p to open the Command Palette, and - **file_suffixes** - path transform; adds these suffixes to filename only - example: `[".js", ".ts", ".tsx"]` +- **enable_file_commands** + - allows disabling file path handling, e.g., if you only want to open web links - **file_custom_commands** - pass a file to shell commands whose pattern matches the file path - example, for copying the file path to the clipboard: `{ "label": "copy path", "commands": "printf '$url' | pbcopy" }` +- **enable_folder_commands** + - allows disabling folder path handling, e.g., if you only want to open web links - **folder_custom_commands** - pass a folder to shell commands whose pattern matches the folder path - example, for opening the folder in iTerm: `{ "label": "open in terminal", "commands": [ "open", "-a", "/Applications/iTerm.app" ] }` diff --git a/open_url.py b/open_url.py index 7dd990b..e8be4f3 100644 --- a/open_url.py +++ b/open_url.py @@ -17,15 +17,21 @@ "Settings", { "delimiters": str, + "delimiters_scoped": list, + "scope_stop": list, "trailing_delimiters": str, "web_browser": str, "web_browser_path": list, + "enable_web_search": bool, "web_searchers": list, + "live_edit": list, "file_prefixes": list, "file_suffixes": list, "search_paths": list, "aliases": dict, + "enable_file_commands": bool, "file_custom_commands": list, + "enable_folder_commands": bool, "folder_custom_commands": list, "other_custom_commands": list, }, @@ -34,15 +40,21 @@ # these are necessary to convert settings object to a dict, which can then be merged with project settings settings_keys = [ "delimiters", + "delimiters_scoped", + "scope_stop", "trailing_delimiters", "web_browser", "web_browser_path", + "enable_web_search", "web_searchers", + "live_edit", "file_prefixes", "file_suffixes", "search_paths", "aliases", + "enable_file_commands", "file_custom_commands", + "enable_folder_commands", "folder_custom_commands", "other_custom_commands", ] @@ -196,6 +208,7 @@ def get_selection(self, region) -> str: """Returns selection. If selection contains no characters, expands it until hitting delimiter chars. """ + view = self.view start: int = region.begin() end: int = region.end() @@ -206,17 +219,40 @@ def get_selection(self, region) -> str: # nothing is selected, so expand selection to nearest delimiters view_size: int = self.view.size() delimiters = list(self.config["delimiters"]) + scope_delim = list(self.config["delimiters_scoped"]) + scope_stop = list(self.config["scope_stop"]) + txt_pt = region.a # use first point for scope matching + txt_scope = view.scope_name(txt_pt) #e.g., "source.python meta.function…" + match_max = 0 + for scope_i in scope_delim: + scope = scope_i['scope'] + match_min = scope_i['min' ] + delim = scope_i['delim'] + if (score := sublime.score_selector(txt_scope, scope)) >= match_min: + if score > match_max: + delimiters = delim + match_max = score # move the selection back to the start of the url while start > 0: if self.view.substr(start - 1) in delimiters: break + if scope_stop: + txt_scope = view.scope_name(start - 1) + for scope_i in scope_stop: + if scope_i in txt_scope: + break start -= 1 # move end of selection forward to the end of the url while end < view_size: if self.view.substr(end) in delimiters: break + if scope_stop: + txt_scope = view.scope_name(end) + for scope_i in scope_stop: + if scope_i in txt_scope: + break end += 1 sel = self.view.substr(sublime.Region(start, end)) return sel.strip() @@ -311,10 +347,19 @@ def modify_or_search_action(self, term: str): """Not a URL and not a local path; prompts user to modify path and looks for it again, or searches for this term using a web searcher. """ - searchers = self.config["web_searchers"] - opts = [f"modify path {term}"] - opts += [f'{s["label"]} ({term})' for s in searchers] - sublime.active_window().show_quick_panel(opts, lambda idx: self.modify_or_search_done(idx, searchers, term)) + is_edit = self.config["live_edit"] + is_web = self.config["enable_web_search"] + if not is_edit and not is_web: + return + + opts, searchers = [], [] + if is_edit: + opts += [f"modify path {term}"] + if is_web: + searchers = self.config["web_searchers"] + opts += [f'{s["label"]} ({term})' for s in searchers] + if opts: + sublime.active_window().show_quick_panel(opts, lambda idx: self.modify_or_search_done(idx, searchers, term)) def modify_or_search_done(self, idx: int, searchers, term: str): if idx < 0: @@ -354,6 +399,8 @@ def other_done(self, idx, openers, path): def folder_action(self, folder: str, show_menu: bool, raw_folder: str): """Choose from folder actions.""" + if not self.config["enable_folder_commands"]: + return openers = match_openers(self.config["folder_custom_commands"], folder) if openers and not show_menu: @@ -361,7 +408,10 @@ def folder_action(self, folder: str, show_menu: bool, raw_folder: str): return opts = [*[opener.get("label") for opener in openers], "search..."] - sublime.active_window().show_quick_panel(opts, lambda idx: self.folder_done(idx, openers, folder, raw_folder)) + sublime.active_window().show_quick_panel(opts, lambda idx: self.folder_done(idx, openers, folder, raw_folder), + sublime.QuickPanelFlags.NONE, -1, None, #selected_index on_highlight + f"📁 {raw_folder}" + ) def folder_done(self, idx: int, openers: list[dict], folder: str, raw_folder: str): if idx < 0: @@ -376,6 +426,8 @@ def folder_done(self, idx: int, openers: list[dict], folder: str, raw_folder: st def file_action(self, path: str, show_menu: bool, raw_path: str) -> None: """Edit file or choose from file actions.""" + if not self.config["enable_file_commands"]: + return openers = match_openers(self.config["file_custom_commands"], path) if not show_menu: @@ -385,6 +437,8 @@ def file_action(self, path: str, show_menu: bool, raw_path: str) -> None: sublime.active_window().show_quick_panel( ["edit", *[opener.get("label") for opener in openers], "search..."], lambda idx: self.file_done(idx, openers, path, raw_path), + sublime.QuickPanelFlags.NONE, -1, None, #selected_index on_highlight + f"␜ {raw_path}" ) def file_done(self, idx: int, openers: list[dict], path: str, raw_path: str): diff --git a/open_url.sublime-settings b/open_url.sublime-settings index 6572633..9236f3a 100644 --- a/open_url.sublime-settings +++ b/open_url.sublime-settings @@ -4,6 +4,16 @@ // delimiters for expanding selection, these are markdown friendly "delimiters": " \t\n\r\"'`,*<>[](){}", + "delimiters_scoped":[ // same as delimiters, but for a specific scope, uses Sublime's score_selector API to test whether a given key matches the scope of the current selection (https://www.sublimetext.com/docs/api_reference.html#sublime.score_selector) + {"scope":"text.html.markdown", "min":2, "delim":" \t\n\r\"'`,*<>[](){}"}, + // scope_match_threshold:↑ min score to consider a comparison a match (0 no match; >0 match, higher is better, see ReadMe for examples) + ], + "scope_stop": [ // stop expanding selection at these scopes + "punctuation.definition.comment", + "punctuation.definition.string.begin","punctuation.definition.string.end", + "punctuation.section.sequence.begin" ,"punctuation.section.sequence.end", + ], + // if these delimiting characters are seen at the end of the URL, they are removed, e.g. sublimetext.com.; becomes sublimetext.com "trailing_delimiters": ";.:", @@ -20,6 +30,9 @@ // example: chrome, linux // "web_browser_path": "/usr/bin/google-chrome %s" + // Enable fallback to web searches + "enable_web_search": true, + // if URL is neither a local file nor a web URL, pass it to a web searcher "web_searchers": [ { @@ -29,6 +42,8 @@ } ], + "live_edit": true, //if selection is not recognized as a file/folder/url, show a panel to edit it + // path transforms "aliases": {}, @@ -38,6 +53,9 @@ "file_suffixes": [".js"], + // Enable file path handling + "enable_file_commands": true, + // pass file that matches regex "pattern" to shell commands "file_custom_commands": [ { "label": "reveal", "os": "osx", "commands": ["open", "-R"] }, @@ -63,6 +81,9 @@ { "label": "open with default application", "os": "linux", "commands": [] } ], + // Enable folder path handling + "enable_folder_commands": true, + // pass folder that matches regex "pattern" to shell commands "folder_custom_commands": [ { "label": "reveal", "os": "osx", "commands": ["open"] },