Skip to content

Instantly share code, notes, and snippets.

@Coderx7
Last active August 29, 2026 06:35
Show Gist options
  • Select an option

  • Save Coderx7/d9307cb5642642ee2f483f91a171ab6f to your computer and use it in GitHub Desktop.

Select an option

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.
#!/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 "$@"
@Coderx7

Coderx7 commented Aug 29, 2026

Copy link
Copy Markdown
Author

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 sudo required. Run the script to install, or use --uninstall to remove the integrations. you can also use --dry-run to see how the changes would look like before actually running the script.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment