Sublime Forum

Sublime text language detection broken. My plugin to fix

#1

It keeps detecting languages wrong, for example, I load multiple bash scripts, all with no extension, so, there is no relying on the extension for detection.
Sublime must be able to detect based on the shebang, and it does, sometimes, but quite often fails.
It’s been detecting files as C++, text, and other failures, with a perfectly valid shebang.

I made a plugin to fix this, and figured I would share, since I can’t be the only one hitting this. From searching, I guess it’s a common Sublime problem.

Run this self-installing bash script.
It can be toggles off/on in the preferences, but there should be no reason to turn it off, unless it detects something wrong. I made a toggle more as a safeguard.

I don’t have any Windows machines up at this time, so I’ve only tested on Mac and Linux.

#!/usr/bin/env bash
# ~/Settings/sublime/sublime_detection_plugin.sh
OSTYPE=$(uname -s)

# Determine Sublime Text User package directory based on OS
if [[ "$OSTYPE" == "Darwin" ]]; then
    TARGET_DIR="$HOME/Library/Application Support/Sublime Text/Packages/User"
else
    TARGET_DIR="$HOME/.config/sublime-text/Packages/User"
fi

PLUGIN_FILE="$TARGET_DIR/script_auto_detector.py"
MENU_FILE="$TARGET_DIR/Main.sublime-menu"

# Ensure target directory exists
mkdir -p "$TARGET_DIR"

# Write Python plugin content with relaxed guard filters and console debugging
cat << 'EOF' > "$PLUGIN_FILE"
import sublime
import sublime_plugin
import re
import os

class ScriptAutoDetectorToggleCommand(sublime_plugin.ApplicationCommand):
    def run(self):
        s = sublime.load_settings("ScriptAutoDetector.sublime-settings")
        enabled = s.get("enabled", True)
        s.set("enabled", not enabled)
        sublime.save_settings("ScriptAutoDetector.sublime-settings")
        status = "Disabled" if enabled else "Enabled"
        sublime.status_message(f"Text Auto-Detector: {status}")

    def is_checked(self):
        s = sublime.load_settings("ScriptAutoDetector.sublime-settings")
        return s.get("enabled", True)

class ScriptAutoDetector(sublime_plugin.EventListener):
    def on_load(self, view):
        sublime.set_timeout(lambda: self.detect_and_set(view), 150)

    def on_new(self, view):
        sublime.set_timeout(lambda: self.detect_and_set(view), 150)

    def on_activated(self, view):
        sublime.set_timeout(lambda: self.detect_and_set(view), 150)

    def detect_and_set(self, view):
        if not view or not view.is_valid():
            return
        
        s = sublime.load_settings("ScriptAutoDetector.sublime-settings")
        if not s.get("enabled", True):
            return

        filename = view.file_name()
        if not filename:
            return

        syntax_obj = view.syntax()
        syntax_name = syntax_obj.name.lower() if syntax_obj else ""
        ext = os.path.splitext(filename)[1]

        # Debug info to help diagnose why a file might be skipped
        print(f"[ScriptAutoDetector] Checking file: {filename} | Ext: '{ext}' | Syntax: '{syntax_name}'")

        # If it has an extension, only run if it's currently set to Plain Text or generic source
        if ext and "plain text" not in syntax_name and not syntax_name.startswith("source"):
            return

        region = sublime.Region(0, min(1024, view.size()))
        header = view.substr(region)

        # Parse shebang interpreter
        shebang_match = re.search(r'^#!\s*(?:.*?[\\/])?([a-zA-Z0-9_-]+)(?:\s+.*)?$', header, re.M)
        interpreter = None
        if shebang_match:
            cmd = shebang_match.group(1).lower()
            if cmd == 'env':
                env_match = re.search(r'^#!\s*(?:.*?[\\/])?env\s+(?:[^\s]+=.*?\s+)*([a-zA-Z0-9_-]+)', header, re.M)
                if env_match:
                    interpreter = env_match.group(1).lower()
            else:
                interpreter = cmd

        syntax_map = {
            'bash': 'Packages/ShellScript/Bash.sublime-syntax',
            'sh': 'Packages/ShellScript/Bash.sublime-syntax',
            'zsh': 'Packages/ShellScript/Bash.sublime-syntax',
            'python': 'Packages/Python/Python.sublime-syntax',
            'python3': 'Packages/Python/Python.sublime-syntax',
            'perl': 'Packages/Perl/Perl.sublime-syntax',
            'ruby': 'Packages/Ruby/Ruby.sublime-syntax'
        }

        if interpreter in syntax_map:
            print(f"[ScriptAutoDetector] Matched shebang interpreter '{interpreter}' for {filename}")
            view.set_syntax_file(syntax_map[interpreter])
            return

        # Check modelines
        header_lower = header.lower()
        if 'mode: shell-script' in header_lower or ' -*- shell-script -*-' in header_lower:
            print(f"[ScriptAutoDetector] Matched shell-script modeline for {filename}")
            view.set_syntax_file('Packages/ShellScript/Bash.sublime-syntax')
        elif 'mode: python' in header_lower or ' -*- python -*-' in header_lower:
            print(f"[ScriptAutoDetector] Matched python modeline for {filename}")
            view.set_syntax_file('Packages/Python/Python.sublime-syntax')
        elif 'mode: perl' in header_lower or ' -*- perl -*-' in header_lower:
            print(f"[ScriptAutoDetector] Matched perl modeline for {filename}")
            view.set_syntax_file('Packages/Perl/Perl.sublime-syntax')
        elif 'mode: ruby' in header_lower or ' -*- ruby -*-' in header_lower:
            print(f"[ScriptAutoDetector] Matched ruby modeline for {filename}")
            view.set_syntax_file('Packages/Ruby/Ruby.sublime-syntax')
EOF

# Write Main Menu integration combining both toggles
cat << 'EOF' > "$MENU_FILE"
[
    {
        "id": "preferences",
        "children":
        [
            { "caption": "-" },
            {
                "caption": "Timestamp On Save",
                "command": "timestamp_on_save_toggle",
                "checkbox": true
            },
            {
                "caption": "Text Auto-Detector",
                "command": "script_auto_detector_toggle",
                "checkbox": true
            }
        ]
    }
]
EOF

chmod 644 "$PLUGIN_FILE"
chmod 644 "$MENU_FILE"
echo "Sublime Text Auto-Detector plugin and menu updated successfully at: $PLUGIN_FILE"
0 Likes