Instantly share code, notes, and snippets.
Last active
August 29, 2026 06:35
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save Coderx7/d9307cb5642642ee2f483f91a171ab6f to your computer and use it in GitHub Desktop.
Adds “Open in VS Code” to the right-click menu on Linux file managers including Nautilus, Dolphin, Thunar, Nemo, and Caja.
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
| #!/usr/bin/env bash | |
| # | |
| # VS Code right-click integration installer for Linux file managers. | |
| # | |
| # Supported: | |
| # - Nautilus | |
| # - Dolphin | |
| # - Thunar | |
| # - Nemo | |
| # - Caja | |
| # | |
| # Usage: | |
| # chmod +x install.sh | |
| # ./install.sh | |
| # ./install.sh --uninstall | |
| # ./install.sh --dry-run | |
| set -Eeuo pipefail | |
| BIN_DIR="${XDG_BIN_HOME:-$HOME/.local/bin}" | |
| WRAPPER="$BIN_DIR/open-in-vscode" | |
| DRY_RUN=0 | |
| UNINSTALL=0 | |
| info() { | |
| printf '\033[1;34m[INFO]\033[0m %s\n' "$*" | |
| } | |
| success() { | |
| printf '\033[1;32m[ OK ]\033[0m %s\n' "$*" | |
| } | |
| warn() { | |
| printf '\033[1;33m[WARN]\033[0m %s\n' "$*" >&2 | |
| } | |
| error() { | |
| printf '\033[1;31m[ERR ]\033[0m %s\n' "$*" >&2 | |
| } | |
| run() { | |
| if (( DRY_RUN )); then | |
| printf '[DRY-RUN] ' | |
| printf '%q ' "$@" | |
| printf '\n' | |
| else | |
| "$@" | |
| fi | |
| } | |
| usage() { | |
| cat <<EOF | |
| Usage: | |
| ./install.sh | |
| ./install.sh --uninstall | |
| ./install.sh --dry-run | |
| EOF | |
| } | |
| for arg in "$@"; do | |
| case "$arg" in | |
| --uninstall) UNINSTALL=1 ;; | |
| --dry-run) DRY_RUN=1 ;; | |
| -h|--help) | |
| usage | |
| exit 0 | |
| ;; | |
| *) | |
| error "Unknown option: $arg" | |
| usage | |
| exit 1 | |
| ;; | |
| esac | |
| done | |
| command_exists() { | |
| command -v "$1" >/dev/null 2>&1 | |
| } | |
| write_file() { | |
| local path="$1" | |
| if (( DRY_RUN )); then | |
| info "Would write: $path" | |
| cat >/dev/null | |
| else | |
| mkdir -p "$(dirname "$path")" | |
| cat >"$path" | |
| fi | |
| } | |
| remove_file() { | |
| local path="$1" | |
| if [[ -e "$path" ]]; then | |
| run rm -f "$path" | |
| success "Removed $path" | |
| fi | |
| } | |
| install_wrapper() { | |
| info "Installing universal VS Code launcher..." | |
| run mkdir -p "$BIN_DIR" | |
| write_file "$WRAPPER" <<'EOF' | |
| #!/usr/bin/env bash | |
| set -Eeuo pipefail | |
| if (( $# == 0 )); then | |
| exit 0 | |
| fi | |
| if command -v code >/dev/null 2>&1; then | |
| exec code "$@" | |
| fi | |
| if command -v codium >/dev/null 2>&1; then | |
| exec codium "$@" | |
| fi | |
| if command -v code-insiders >/dev/null 2>&1; then | |
| exec code-insiders "$@" | |
| fi | |
| if command -v flatpak >/dev/null 2>&1 && | |
| flatpak info com.visualstudio.code >/dev/null 2>&1; then | |
| exec flatpak run com.visualstudio.code "$@" | |
| fi | |
| if command -v flatpak >/dev/null 2>&1 && | |
| flatpak info com.vscodium.codium >/dev/null 2>&1; then | |
| exec flatpak run com.vscodium.codium "$@" | |
| fi | |
| if command -v notify-send >/dev/null 2>&1; then | |
| notify-send \ | |
| "VS Code not found" \ | |
| "Install VS Code, VSCodium, or make the 'code' command available." | |
| fi | |
| echo "Could not find VS Code." >&2 | |
| exit 127 | |
| EOF | |
| if (( ! DRY_RUN )); then | |
| chmod +x "$WRAPPER" | |
| fi | |
| success "Installed launcher: $WRAPPER" | |
| } | |
| install_nautilus() { | |
| local ext_dir="${XDG_DATA_HOME:-$HOME/.local/share}/nautilus-python/extensions" | |
| local ext_file="$ext_dir/vscode_context_menu.py" | |
| info "Installing Nautilus integration..." | |
| write_file "$ext_file" <<EOF | |
| from gi.repository import Nautilus, GObject | |
| import subprocess | |
| import os | |
| WRAPPER = os.path.expanduser("${WRAPPER}") | |
| class VSCodeContextMenu(GObject.GObject, Nautilus.MenuProvider): | |
| def _open(self, menu, files): | |
| paths = [] | |
| for file in files: | |
| location = file.get_location() | |
| if location: | |
| path = location.get_path() | |
| if path: | |
| paths.append(path) | |
| if paths: | |
| subprocess.Popen([WRAPPER] + paths) | |
| def get_file_items(self, files): | |
| if not files: | |
| return [] | |
| item = Nautilus.MenuItem( | |
| name="VSCodeContextMenu::Open", | |
| label="Open in VS Code", | |
| tip="Open the selected item(s) in Visual Studio Code" | |
| ) | |
| item.connect("activate", self._open, files) | |
| return [item] | |
| def get_background_items(self, current_folder): | |
| if not current_folder: | |
| return [] | |
| item = Nautilus.MenuItem( | |
| name="VSCodeContextMenu::OpenHere", | |
| label="Open this folder in VS Code", | |
| tip="Open the current folder in Visual Studio Code" | |
| ) | |
| item.connect("activate", self._open, [current_folder]) | |
| return [item] | |
| EOF | |
| success "Installed Nautilus extension" | |
| if command_exists nautilus && (( ! DRY_RUN )); then | |
| nautilus -q >/dev/null 2>&1 || true | |
| fi | |
| } | |
| install_dolphin() { | |
| local dir | |
| local file | |
| info "Installing Dolphin integration..." | |
| if [[ -d "$HOME/.local/share/kservices5" ]]; then | |
| dir="$HOME/.local/share/kservices5/ServiceMenus" | |
| else | |
| dir="$HOME/.local/share/kio/servicemenus" | |
| fi | |
| file="$dir/open-in-vscode.desktop" | |
| write_file "$file" <<EOF | |
| [Desktop Entry] | |
| Type=Service | |
| MimeType=inode/directory; | |
| Actions=openInVSCode; | |
| X-KDE-Priority=TopLevel | |
| [Desktop Action openInVSCode] | |
| Name=Open in VS Code | |
| Icon=code | |
| Exec=${WRAPPER} %f | |
| EOF | |
| success "Installed Dolphin Service Menu" | |
| } | |
| install_thunar() { | |
| local config="${XDG_CONFIG_HOME:-$HOME/.config}/Thunar/uca.xml" | |
| info "Installing Thunar integration..." | |
| if [[ -f "$config" ]] && | |
| grep -q "vscode-context-menu" "$config"; then | |
| success "Thunar action already installed" | |
| return | |
| fi | |
| if [[ ! -f "$config" ]]; then | |
| write_file "$config" <<'EOF' | |
| <?xml version="1.0" encoding="UTF-8"?> | |
| <actions> | |
| </actions> | |
| EOF | |
| fi | |
| if (( DRY_RUN )); then | |
| info "Would add VS Code action to $config" | |
| return | |
| fi | |
| local tmp | |
| tmp="$(mktemp)" | |
| sed '$d' "$config" >"$tmp" | |
| cat >>"$tmp" <<EOF | |
| <action> | |
| <icon>code</icon> | |
| <name>Open in VS Code</name> | |
| <unique-id>vscode-context-menu</unique-id> | |
| <command>${WRAPPER} %F</command> | |
| <description>Open selected files or folders in Visual Studio Code</description> | |
| <patterns>*</patterns> | |
| <directories/> | |
| </action> | |
| </actions> | |
| EOF | |
| mv "$tmp" "$config" | |
| success "Installed Thunar custom action" | |
| if command_exists thunar; then | |
| thunar -q >/dev/null 2>&1 || true | |
| fi | |
| } | |
| install_nemo() { | |
| local dir="${XDG_DATA_HOME:-$HOME/.local/share}/nemo/actions" | |
| local file="$dir/open-in-vscode.nemo_action" | |
| info "Installing Nemo integration..." | |
| write_file "$file" <<EOF | |
| [Nemo Action] | |
| Name=Open in VS Code | |
| Comment=Open selected files or folders in Visual Studio Code | |
| Exec=${WRAPPER} %F | |
| Selection=any | |
| Extensions=any | |
| Icon-Name=code | |
| Quote=double | |
| Dependencies=exec ${WRAPPER} | |
| EOF | |
| success "Installed Nemo action" | |
| if command_exists nemo && (( ! DRY_RUN )); then | |
| nemo -q >/dev/null 2>&1 || true | |
| fi | |
| } | |
| install_caja() { | |
| local dir="${XDG_CONFIG_HOME:-$HOME/.config}/caja/scripts" | |
| local file="$dir/Open in VS Code" | |
| info "Installing Caja integration..." | |
| write_file "$file" <<EOF | |
| #!/usr/bin/env bash | |
| "${WRAPPER}" "\$@" | |
| EOF | |
| if (( ! DRY_RUN )); then | |
| chmod +x "$file" | |
| fi | |
| success "Installed Caja script" | |
| if command_exists caja && (( ! DRY_RUN )); then | |
| caja -q >/dev/null 2>&1 || true | |
| fi | |
| } | |
| install_generic_launcher() { | |
| local applications="${XDG_DATA_HOME:-$HOME/.local/share}/applications" | |
| local desktop_file="$applications/open-in-vscode.desktop" | |
| info "Installing generic launcher..." | |
| write_file "$desktop_file" <<EOF | |
| [Desktop Entry] | |
| Version=1.0 | |
| Type=Application | |
| Name=Open in VS Code | |
| Comment=Open files and folders in Visual Studio Code | |
| Exec=${WRAPPER} %F | |
| Icon=code | |
| Terminal=false | |
| MimeType=inode/directory;text/plain; | |
| Categories=Development;IDE;TextEditor; | |
| EOF | |
| success "Installed generic launcher" | |
| } | |
| detect_file_managers() { | |
| local found=0 | |
| info "Detecting installed file managers..." | |
| if command_exists nautilus; then | |
| info "Detected Nautilus" | |
| install_nautilus | |
| found=1 | |
| fi | |
| if command_exists dolphin; then | |
| info "Detected Dolphin" | |
| install_dolphin | |
| found=1 | |
| fi | |
| if command_exists thunar; then | |
| info "Detected Thunar" | |
| install_thunar | |
| found=1 | |
| fi | |
| if command_exists nemo; then | |
| info "Detected Nemo" | |
| install_nemo | |
| found=1 | |
| fi | |
| if command_exists caja; then | |
| info "Detected Caja" | |
| install_caja | |
| found=1 | |
| fi | |
| if (( ! found )); then | |
| warn "No supported file manager detected." | |
| fi | |
| install_generic_launcher | |
| } | |
| uninstall_all() { | |
| info "Removing VS Code context-menu integrations..." | |
| remove_file "$WRAPPER" | |
| remove_file \ | |
| "${XDG_DATA_HOME:-$HOME/.local/share}/nautilus-python/extensions/vscode_context_menu.py" | |
| remove_file \ | |
| "$HOME/.local/share/kio/servicemenus/open-in-vscode.desktop" | |
| remove_file \ | |
| "$HOME/.local/share/kservices5/ServiceMenus/open-in-vscode.desktop" | |
| remove_file \ | |
| "${XDG_DATA_HOME:-$HOME/.local/share}/nemo/actions/open-in-vscode.nemo_action" | |
| remove_file \ | |
| "${XDG_CONFIG_HOME:-$HOME/.config}/caja/scripts/Open in VS Code" | |
| remove_file \ | |
| "${XDG_DATA_HOME:-$HOME/.local/share}/applications/open-in-vscode.desktop" | |
| local thunar_config="${XDG_CONFIG_HOME:-$HOME/.config}/Thunar/uca.xml" | |
| if [[ -f "$thunar_config" ]] && | |
| grep -q "vscode-context-menu" "$thunar_config"; then | |
| if (( ! DRY_RUN )); then | |
| python3 - "$thunar_config" <<'PY' | |
| import sys | |
| import xml.etree.ElementTree as ET | |
| path = sys.argv[1] | |
| tree = ET.parse(path) | |
| root = tree.getroot() | |
| for action in list(root): | |
| uid = action.find("unique-id") | |
| if uid is not None and uid.text == "vscode-context-menu": | |
| root.remove(action) | |
| tree.write( | |
| path, | |
| encoding="UTF-8", | |
| xml_declaration=True | |
| ) | |
| PY | |
| fi | |
| success "Removed Thunar action" | |
| fi | |
| command_exists nautilus && nautilus -q >/dev/null 2>&1 || true | |
| command_exists nemo && nemo -q >/dev/null 2>&1 || true | |
| command_exists caja && caja -q >/dev/null 2>&1 || true | |
| command_exists thunar && thunar -q >/dev/null 2>&1 || true | |
| success "Uninstallation complete" | |
| } | |
| main() { | |
| if (( UNINSTALL )); then | |
| uninstall_all | |
| exit 0 | |
| fi | |
| install_wrapper | |
| detect_file_managers | |
| success "Installation complete." | |
| echo | |
| echo "XDG_CURRENT_DESKTOP=${XDG_CURRENT_DESKTOP:-unknown}" | |
| echo "Wrapper: $WRAPPER" | |
| } | |
| main "$@" |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is a Linux script that adds an “Open in VS Code” option to the right-click menu of basically all major Linux file managers.
It automatically detects and installs integrations for Nautilus, Dolphin, Thunar, Nemo, and Caja, making it useful across major Linux distributions such as Ubuntu, Arch, Fedora, etc and desktop environments.
It also supports VS Code, VSCodium, Code Insiders, and Flatpak installations.
No
sudorequired. Run the script to install, or use--uninstallto remove the integrations. you can also use--dry-runto see how the changes would look like before actually running the script.