From 620eea8bf7da2e8082be7081f08e17c44a891c64 Mon Sep 17 00:00:00 2001 From: xianghongai Date: Fri, 19 Dec 2025 19:47:15 +0800 Subject: [PATCH] Allow users to choose between VS Code stable (code) and Insiders (code-insiders) - Add interactive installer prompts to select which versions to register - Store user preferences in ~/.config/code-nautilus/targets.conf - Add local test mode (--local/-l) for easier development and testing - Improve code documentation with detailed comments and docstrings - Support custom VS Code paths via VSCODE_BIN and VSCODE_INSIDERS_BIN environment variables - Update README with configuration instructions and local installation guide - Fix bash syntax issues (use `! command` instead of checking $?) - Add platform-specific comments for package managers (Arch/Debian/Fedora) --- README.md | 21 ++++++ code-nautilus.py | 177 ++++++++++++++++++++++++++++++++++++++++------- install.sh | 114 +++++++++++++++++++++++++++--- 3 files changed, 276 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index b93af12..49a4f11 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,27 @@ This repo provides a visual studio code extension for Nautilus. wget -qO- https://raw.githubusercontent.com/harry-cpp/code-nautilus/master/install.sh | bash ``` +During the install process you will be prompted to choose whether you want to register the stable `code` binary, `code-insiders`, or both inside Nautilus. Your choices are stored in `~/.config/code-nautilus/targets.conf`, so you can rerun the installer any time to update them. + +## Local Installation (for development) + +If you want to install from a local copy (for testing changes or contributing): + +```bash +# Clone the repository +git clone https://github.com/harry-cpp/code-nautilus.git +cd code-nautilus + +# Install using local files +bash install.sh --local +``` + +The `--local` (or `-l`) flag tells the installer to use your local `code-nautilus.py` file instead of downloading from GitHub. This is useful when: + +- Testing local changes before contributing +- Developing new features +- Running the extension from a forked repository + ## Uninstall Extension ``` diff --git a/code-nautilus.py b/code-nautilus.py index c4e56ea..b34d2e9 100644 --- a/code-nautilus.py +++ b/code-nautilus.py @@ -1,62 +1,187 @@ # VSCode Nautilus Extension # -# Place me in ~/.local/share/nautilus-python/extensions/, -# ensure you have python-nautilus package, restart Nautilus, and enjoy :) +# Adds "Open in Code" context menu items to Nautilus file manager. +# Supports both VS Code stable and Insiders versions. +# +# Installation: +# Place in ~/.local/share/nautilus-python/extensions/ +# Ensure python-nautilus package is installed +# Restart Nautilus +# +# Configuration: +# Edit ~/.config/code-nautilus/targets.conf to enable/disable specific VS Code versions # # This script is released to the public domain. from gi.repository import Nautilus, GObject from subprocess import call import os +import shutil + +# Configuration file location +CONFIG_DIR = os.path.join(os.path.expanduser('~'), '.config', 'code-nautilus') +CONFIG_FILE = os.path.join(CONFIG_DIR, 'targets.conf') + +# Available VS Code targets +# Format: 'config-key': ('Display Name', 'Environment Variable', 'Default Command') +TARGET_OPTIONS = { + 'code': ('Code', 'VSCODE_BIN', 'code'), + 'code-insiders': ('Code - Insiders', 'VSCODE_INSIDERS_BIN', 'code-insiders'), +} + + +def _register_editor(name, env_var, default_cmd, targets): + """ + Register a VS Code editor if it exists on the system. + + Args: + name: Display name for the menu item (e.g., "Code", "Code - Insiders") + env_var: Environment variable to check for custom command path + default_cmd: Default command to use if env var is not set + targets: List to append the registered editor to + """ + cmd = os.environ.get(env_var) + cmd = cmd.strip() if cmd else default_cmd + if not cmd: + return + # Only register if the command is actually available on the system + if shutil.which(cmd): + targets.append((name, cmd)) + + +def _load_configured_target_keys(): + """ + Load which VS Code targets are enabled from the configuration file. + + Reads ~/.config/code-nautilus/targets.conf and returns a list of + enabled target keys (e.g., ['code', 'code-insiders']). -# path to vscode -VSCODE = 'code' + Configuration format: + code=1 + code-insiders=0 -# what name do you want to see in the context menu? -VSCODENAME = 'Code' + Returns: + List of enabled target keys. Defaults to ['code'] if config is missing + or no targets are enabled. + """ + selected = [] + if os.path.exists(CONFIG_FILE): + try: + with open(CONFIG_FILE, 'r') as config: + for raw_line in config: + line = raw_line.strip() + # Skip empty lines and comments + if not line or line.startswith('#') or '=' not in line: + continue + key, value = line.split('=', 1) + # Only process valid target keys + if key.strip() not in TARGET_OPTIONS: + continue + # Check if target is enabled (value is truthy) + if value.strip().lower() in ('1', 'true', 'yes', 'y'): + selected.append(key.strip()) + except OSError: + pass -# always create new window? + # Default to stable VS Code if no configuration found + if not selected: + selected.append('code') + return selected + + +# Build list of available VS Code targets based on configuration and system availability +VSCODE_TARGETS = [] +for target_key in _load_configured_target_keys(): + option = TARGET_OPTIONS.get(target_key) + if option: + _register_editor(option[0], option[1], option[2], VSCODE_TARGETS) + +# Fallback: if no configured targets are available, default to stable VS Code +if not VSCODE_TARGETS: + fallback = TARGET_OPTIONS['code'] + VSCODE_TARGETS.append((fallback[0], fallback[2])) + +# Set to True to always open files in a new VS Code window +# When False, files/folders will open in existing window unless a folder is opened NEWWINDOW = False class VSCodeExtension(GObject.GObject, Nautilus.MenuProvider): + """ + Nautilus extension that adds VS Code context menu items. + + Provides two types of menu items: + 1. File items: When right-clicking on files/folders + 2. Background items: When right-clicking on empty space in a directory + """ - def launch_vscode(self, menu, files): + def launch_vscode(self, menu, data): + """ + Launch VS Code with the selected files/folders. + + Args: + menu: The menu item that was clicked (unused) + data: Tuple of (files, executable) where: + - files: List of Nautilus file objects + - executable: Path to VS Code executable + """ + files, executable = data safepaths = '' args = '' for file in files: filepath = file.get_location().get_path() + # Quote paths to handle spaces and special characters safepaths += '"' + filepath + '" ' - # If one of the files we are trying to open is a folder - # create a new instance of vscode + # If opening a folder, always create a new VS Code window + # This prevents folders from opening as workspace additions if os.path.isdir(filepath) and os.path.exists(filepath): args = '--new-window ' + # Force new window if NEWWINDOW is enabled if NEWWINDOW: args = '--new-window ' - call(VSCODE + ' ' + args + safepaths + '&', shell=True) + # Execute VS Code in background + call(executable + ' ' + args + safepaths + '&', shell=True) def get_file_items(self, *args): + """ + Create context menu items when right-clicking on files/folders. + + Returns: + List of Nautilus.MenuItem objects, one for each enabled VS Code variant + """ files = args[-1] - item = Nautilus.MenuItem( - name='VSCodeOpen', - label='Open in ' + VSCODENAME, - tip='Opens the selected files with VSCode' - ) - item.connect('activate', self.launch_vscode, files) + items = [] + for idx, (name, executable) in enumerate(VSCODE_TARGETS): + item = Nautilus.MenuItem( + name='VSCodeOpen{0}'.format(idx), + label='Open in ' + name, + tip='Opens the selected files with VSCode' + ) + item.connect('activate', self.launch_vscode, (files, executable)) + items.append(item) - return [item] + return items def get_background_items(self, *args): + """ + Create context menu items when right-clicking on empty space in a directory. + + Returns: + List of Nautilus.MenuItem objects for opening the current directory + """ file_ = args[-1] - item = Nautilus.MenuItem( - name='VSCodeOpenBackground', - label='Open in ' + VSCODENAME, - tip='Opens the current directory in VSCode' - ) - item.connect('activate', self.launch_vscode, [file_]) - - return [item] + items = [] + for idx, (name, executable) in enumerate(VSCODE_TARGETS): + item = Nautilus.MenuItem( + name='VSCodeOpenBackground{0}'.format(idx), + label='Open in ' + name, + tip='Opens the current directory in VSCode' + ) + item.connect('activate', self.launch_vscode, ([file_], executable)) + items.append(item) + + return items diff --git a/install.sh b/install.sh index b2b4e96..32ec155 100755 --- a/install.sh +++ b/install.sh @@ -1,12 +1,94 @@ #!/bin/bash -# Install python-nautilus +# Check if running in local test mode +# When --local or -l flag is provided, the script will copy the local code-nautilus.py +# instead of downloading from GitHub. This is useful for testing local changes. +LOCAL_MODE=false +if [ "$1" = "--local" ] || [ "$1" = "-l" ]; then + LOCAL_MODE=true + echo "Local test mode enabled" +fi + +# Helper function to ask yes/no questions with default answer +# Args: +# $1 - The prompt message to display +# $2 - The default answer (Y or N) +# Returns: +# 0 for yes, 1 for no +ask_yes_no() { + local prompt="$1" + local default_answer="$2" + local reply + while true + do + read -r -p "$prompt" reply + if [ -z "$reply" ] + then + reply="$default_answer" + fi + case "$reply" in + [yY]) return 0 ;; + [nN]) return 1 ;; + *) echo "Please enter y or n." ;; + esac + done +} + +# Configure which VS Code versions to register in Nautilus context menu +# Creates a configuration file that the Python extension reads to determine +# which VS Code variants should appear in the right-click menu. +# Configuration is saved to ~/.config/code-nautilus/targets.conf +configure_targets() { + local config_dir="$HOME/.config/code-nautilus" + local config_file="$config_dir/targets.conf" + mkdir -p "$config_dir" + + echo "Select VS Code version(s) to register in Nautilus:" + local register_code + local register_insiders + + # Ask if user wants to register stable VS Code + if ask_yes_no " - Register stable VS Code (code)? [Y/n] " "Y" + then + register_code=1 + else + register_code=0 + fi + + # Ask if user wants to register VS Code Insiders + if ask_yes_no " - Register VS Code Insiders (code-insiders)? [y/N] " "N" + then + register_insiders=1 + else + register_insiders=0 + fi + + # If neither version is selected, default to stable version + if [ "$register_code" -eq 0 ] && [ "$register_insiders" -eq 0 ] + then + echo "No version selected. Defaulting to stable VS Code." + register_code=1 + fi + + # Write configuration file + # Format: key=value where value is 1 (enabled) or 0 (disabled) + cat > "$config_file" < /dev/null 2>&1 then - # check if already install, else install - pacman -Qi python-nautilus &> /dev/null - if [ `echo $?` -eq 1 ] + # Arch Linux / Manjaro + if ! pacman -Qi python-nautilus &> /dev/null then sudo pacman -S --noconfirm python-nautilus else @@ -14,7 +96,8 @@ then fi elif type "apt-get" > /dev/null 2>&1 then - # Find Ubuntu python-nautilus package + # Debian / Ubuntu + # Package name varies by Ubuntu version (python-nautilus vs python3-nautilus) package_name="python-nautilus" found_package=$(apt-cache search --names-only $package_name) if [ -z "$found_package" ] @@ -22,7 +105,7 @@ then package_name="python3-nautilus" fi - # Check if the package needs to be installed and install it + # Check if the package is already installed installed=$(apt list --installed $package_name -qq 2> /dev/null) if [ -z "$installed" ] then @@ -32,6 +115,7 @@ then fi elif type "dnf" > /dev/null 2>&1 then + # Fedora / RHEL installed=`dnf list --installed nautilus-python 2> /dev/null` if [ -z "$installed" ] then @@ -43,17 +127,27 @@ else echo "Failed to find python-nautilus, please install it manually." fi -# Remove previous version and setup folder +# Remove previous versions and ensure extension directory exists +# VSCodeExtension.py is the old filename, code-nautilus.py is the new one echo "Removing previous version (if found)..." mkdir -p ~/.local/share/nautilus-python/extensions rm -f ~/.local/share/nautilus-python/extensions/VSCodeExtension.py rm -f ~/.local/share/nautilus-python/extensions/code-nautilus.py # Download and install the extension -echo "Downloading newest version..." -wget -q -O ~/.local/share/nautilus-python/extensions/code-nautilus.py https://raw.githubusercontent.com/harry-cpp/code-nautilus/master/code-nautilus.py +# In local mode, copy from current directory; otherwise download from GitHub +if [ "$LOCAL_MODE" = true ]; then + echo "Using local version for testing..." + cp "$(dirname "$0")/code-nautilus.py" ~/.local/share/nautilus-python/extensions/code-nautilus.py +else + echo "Downloading newest version..." + wget -q -O ~/.local/share/nautilus-python/extensions/code-nautilus.py https://raw.githubusercontent.com/harry-cpp/code-nautilus/master/code-nautilus.py +fi + +# Prompt user to configure which VS Code versions to register +configure_targets -# Restart nautilus +# Restart Nautilus to load the new extension echo "Restarting nautilus..." nautilus -q