Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,17 @@ To customize these, hit <kbd>shift+cmd+p</kbd> 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**
Expand All @@ -145,9 +156,13 @@ To customize these, hit <kbd>shift+cmd+p</kbd> 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" }`
Expand All @@ -160,9 +175,13 @@ To customize these, hit <kbd>shift+cmd+p</kbd> 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" ] }`
Expand Down
64 changes: 59 additions & 5 deletions open_url.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand All @@ -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",
]
Expand Down Expand Up @@ -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()

Expand All @@ -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()
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -354,14 +399,19 @@ 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:
self.folder_done(0, openers, folder, raw_folder)
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:
Expand All @@ -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:
Expand All @@ -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):
Expand Down
21 changes: 21 additions & 0 deletions open_url.sublime-settings
Original file line number Diff line number Diff line change
Expand Up @@ -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": ";.:",

Expand All @@ -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": [
{
Expand All @@ -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": {},

Expand All @@ -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"] },
Expand All @@ -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"] },
Expand Down