|
1 | 1 | # VSCode Nautilus Extension |
2 | 2 | # |
3 | | -# Place me in ~/.local/share/nautilus-python/extensions/, |
4 | | -# ensure you have python-nautilus package, restart Nautilus, and enjoy :) |
| 3 | +# Adds "Open in Code" context menu items to Nautilus file manager. |
| 4 | +# Supports both VS Code stable and Insiders versions. |
| 5 | +# |
| 6 | +# Installation: |
| 7 | +# Place in ~/.local/share/nautilus-python/extensions/ |
| 8 | +# Ensure python-nautilus package is installed |
| 9 | +# Restart Nautilus |
| 10 | +# |
| 11 | +# Configuration: |
| 12 | +# Edit ~/.config/code-nautilus/targets.conf to enable/disable specific VS Code versions |
5 | 13 | # |
6 | 14 | # This script is released to the public domain. |
7 | 15 |
|
8 | 16 | from gi.repository import Nautilus, GObject |
9 | 17 | from subprocess import call |
10 | 18 | import os |
| 19 | +import shutil |
| 20 | + |
| 21 | +# Configuration file location |
| 22 | +CONFIG_DIR = os.path.join(os.path.expanduser('~'), '.config', 'code-nautilus') |
| 23 | +CONFIG_FILE = os.path.join(CONFIG_DIR, 'targets.conf') |
| 24 | + |
| 25 | +# Available VS Code targets |
| 26 | +# Format: 'config-key': ('Display Name', 'Environment Variable', 'Default Command') |
| 27 | +TARGET_OPTIONS = { |
| 28 | + 'code': ('Code', 'VSCODE_BIN', 'code'), |
| 29 | + 'code-insiders': ('Code - Insiders', 'VSCODE_INSIDERS_BIN', 'code-insiders'), |
| 30 | +} |
| 31 | + |
| 32 | + |
| 33 | +def _register_editor(name, env_var, default_cmd, targets): |
| 34 | + """ |
| 35 | + Register a VS Code editor if it exists on the system. |
| 36 | +
|
| 37 | + Args: |
| 38 | + name: Display name for the menu item (e.g., "Code", "Code - Insiders") |
| 39 | + env_var: Environment variable to check for custom command path |
| 40 | + default_cmd: Default command to use if env var is not set |
| 41 | + targets: List to append the registered editor to |
| 42 | + """ |
| 43 | + cmd = os.environ.get(env_var) |
| 44 | + cmd = cmd.strip() if cmd else default_cmd |
| 45 | + if not cmd: |
| 46 | + return |
| 47 | + # Only register if the command is actually available on the system |
| 48 | + if shutil.which(cmd): |
| 49 | + targets.append((name, cmd)) |
| 50 | + |
| 51 | + |
| 52 | +def _load_configured_target_keys(): |
| 53 | + """ |
| 54 | + Load which VS Code targets are enabled from the configuration file. |
| 55 | +
|
| 56 | + Reads ~/.config/code-nautilus/targets.conf and returns a list of |
| 57 | + enabled target keys (e.g., ['code', 'code-insiders']). |
11 | 58 |
|
12 | | -# path to vscode |
13 | | -VSCODE = 'code' |
| 59 | + Configuration format: |
| 60 | + code=1 |
| 61 | + code-insiders=0 |
14 | 62 |
|
15 | | -# what name do you want to see in the context menu? |
16 | | -VSCODENAME = 'Code' |
| 63 | + Returns: |
| 64 | + List of enabled target keys. Defaults to ['code'] if config is missing |
| 65 | + or no targets are enabled. |
| 66 | + """ |
| 67 | + selected = [] |
| 68 | + if os.path.exists(CONFIG_FILE): |
| 69 | + try: |
| 70 | + with open(CONFIG_FILE, 'r') as config: |
| 71 | + for raw_line in config: |
| 72 | + line = raw_line.strip() |
| 73 | + # Skip empty lines and comments |
| 74 | + if not line or line.startswith('#') or '=' not in line: |
| 75 | + continue |
| 76 | + key, value = line.split('=', 1) |
| 77 | + # Only process valid target keys |
| 78 | + if key.strip() not in TARGET_OPTIONS: |
| 79 | + continue |
| 80 | + # Check if target is enabled (value is truthy) |
| 81 | + if value.strip().lower() in ('1', 'true', 'yes', 'y'): |
| 82 | + selected.append(key.strip()) |
| 83 | + except OSError: |
| 84 | + pass |
17 | 85 |
|
18 | | -# always create new window? |
| 86 | + # Default to stable VS Code if no configuration found |
| 87 | + if not selected: |
| 88 | + selected.append('code') |
| 89 | + return selected |
| 90 | + |
| 91 | + |
| 92 | +# Build list of available VS Code targets based on configuration and system availability |
| 93 | +VSCODE_TARGETS = [] |
| 94 | +for target_key in _load_configured_target_keys(): |
| 95 | + option = TARGET_OPTIONS.get(target_key) |
| 96 | + if option: |
| 97 | + _register_editor(option[0], option[1], option[2], VSCODE_TARGETS) |
| 98 | + |
| 99 | +# Fallback: if no configured targets are available, default to stable VS Code |
| 100 | +if not VSCODE_TARGETS: |
| 101 | + fallback = TARGET_OPTIONS['code'] |
| 102 | + VSCODE_TARGETS.append((fallback[0], fallback[2])) |
| 103 | + |
| 104 | +# Set to True to always open files in a new VS Code window |
| 105 | +# When False, files/folders will open in existing window unless a folder is opened |
19 | 106 | NEWWINDOW = False |
20 | 107 |
|
21 | 108 |
|
22 | 109 | class VSCodeExtension(GObject.GObject, Nautilus.MenuProvider): |
| 110 | + """ |
| 111 | + Nautilus extension that adds VS Code context menu items. |
| 112 | +
|
| 113 | + Provides two types of menu items: |
| 114 | + 1. File items: When right-clicking on files/folders |
| 115 | + 2. Background items: When right-clicking on empty space in a directory |
| 116 | + """ |
23 | 117 |
|
24 | | - def launch_vscode(self, menu, files): |
| 118 | + def launch_vscode(self, menu, data): |
| 119 | + """ |
| 120 | + Launch VS Code with the selected files/folders. |
| 121 | +
|
| 122 | + Args: |
| 123 | + menu: The menu item that was clicked (unused) |
| 124 | + data: Tuple of (files, executable) where: |
| 125 | + - files: List of Nautilus file objects |
| 126 | + - executable: Path to VS Code executable |
| 127 | + """ |
| 128 | + files, executable = data |
25 | 129 | safepaths = '' |
26 | 130 | args = '' |
27 | 131 |
|
28 | 132 | for file in files: |
29 | 133 | filepath = file.get_location().get_path() |
| 134 | + # Quote paths to handle spaces and special characters |
30 | 135 | safepaths += '"' + filepath + '" ' |
31 | 136 |
|
32 | | - # If one of the files we are trying to open is a folder |
33 | | - # create a new instance of vscode |
| 137 | + # If opening a folder, always create a new VS Code window |
| 138 | + # This prevents folders from opening as workspace additions |
34 | 139 | if os.path.isdir(filepath) and os.path.exists(filepath): |
35 | 140 | args = '--new-window ' |
36 | 141 |
|
| 142 | + # Force new window if NEWWINDOW is enabled |
37 | 143 | if NEWWINDOW: |
38 | 144 | args = '--new-window ' |
39 | 145 |
|
40 | | - call(VSCODE + ' ' + args + safepaths + '&', shell=True) |
| 146 | + # Execute VS Code in background |
| 147 | + call(executable + ' ' + args + safepaths + '&', shell=True) |
41 | 148 |
|
42 | 149 | def get_file_items(self, *args): |
| 150 | + """ |
| 151 | + Create context menu items when right-clicking on files/folders. |
| 152 | +
|
| 153 | + Returns: |
| 154 | + List of Nautilus.MenuItem objects, one for each enabled VS Code variant |
| 155 | + """ |
43 | 156 | files = args[-1] |
44 | | - item = Nautilus.MenuItem( |
45 | | - name='VSCodeOpen', |
46 | | - label='Open in ' + VSCODENAME, |
47 | | - tip='Opens the selected files with VSCode' |
48 | | - ) |
49 | | - item.connect('activate', self.launch_vscode, files) |
| 157 | + items = [] |
| 158 | + for idx, (name, executable) in enumerate(VSCODE_TARGETS): |
| 159 | + item = Nautilus.MenuItem( |
| 160 | + name='VSCodeOpen{0}'.format(idx), |
| 161 | + label='Open in ' + name, |
| 162 | + tip='Opens the selected files with VSCode' |
| 163 | + ) |
| 164 | + item.connect('activate', self.launch_vscode, (files, executable)) |
| 165 | + items.append(item) |
50 | 166 |
|
51 | | - return [item] |
| 167 | + return items |
52 | 168 |
|
53 | 169 | def get_background_items(self, *args): |
| 170 | + """ |
| 171 | + Create context menu items when right-clicking on empty space in a directory. |
| 172 | +
|
| 173 | + Returns: |
| 174 | + List of Nautilus.MenuItem objects for opening the current directory |
| 175 | + """ |
54 | 176 | file_ = args[-1] |
55 | | - item = Nautilus.MenuItem( |
56 | | - name='VSCodeOpenBackground', |
57 | | - label='Open in ' + VSCODENAME, |
58 | | - tip='Opens the current directory in VSCode' |
59 | | - ) |
60 | | - item.connect('activate', self.launch_vscode, [file_]) |
61 | | - |
62 | | - return [item] |
| 177 | + items = [] |
| 178 | + for idx, (name, executable) in enumerate(VSCODE_TARGETS): |
| 179 | + item = Nautilus.MenuItem( |
| 180 | + name='VSCodeOpenBackground{0}'.format(idx), |
| 181 | + label='Open in ' + name, |
| 182 | + tip='Opens the current directory in VSCode' |
| 183 | + ) |
| 184 | + item.connect('activate', self.launch_vscode, ([file_], executable)) |
| 185 | + items.append(item) |
| 186 | + |
| 187 | + return items |
0 commit comments