Sublime Forum

Automatically set SQL keywords to upper case

#1

Automatically set SQL keywords to upper case

This feature may be found on some SQL tools as pgAdmin:

Similar requests:

  1. A macro to upper case all sql keywords
  2. ORACLE keywords automatically capitalized
0 Likes

#2

Here you go, this will uppercase the preceding SQL keyword when you press Space:

keybinding:

{ "keys": [" "], "command": "upper_case_previous_item_and_insert_space",
    "context":
    [
        { "key": "selector", "operator": "equal", "operand": "source.sql", "match_all": true },
        { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true },
        { "key": "scope_before_cursor", "operator": "equal", "operand": "keyword", "match_all": true },
        //{ "key": "preceding_text", "operator": "regex_match", "operand": "[a-z]+$", "match_all": true },
    ]
},

sql_keyword_uppercase.py:

import sublime
import sublime_plugin


class UpperCasePreviousItemAndInsertSpaceCommand(sublime_plugin.TextCommand):
    def run(self, edit):
        selections = [cursor for cursor in self.view.sel()] # can't use `view.sel()[:]` because it gives an error `TypeError: an integer is required`
        to_uppercase = []
        for sel in selections:
            if sel.end() > 0:
                #prev_item = self.view.extract_scope(sel.end() - 1)
                scope = self.view.scope_name(sel.end() - 1)
                begin = sel.end() - 1
                while self.view.scope_name(begin) == scope and begin > 0:
                    begin -= 1
                prev_item = sublime.Region(begin, sel.end())
                to_uppercase.append(prev_item)
        self.view.sel().clear()
        self.view.sel().add_all(to_uppercase)
        self.view.run_command('upper_case')
        self.view.sel().clear()
        self.view.sel().add_all(selections)
        self.view.run_command('insert', { 'characters': ' ' })

class ScopeBeforeCursorEventListener(sublime_plugin.EventListener):
    def on_query_context(self, view, key, operator, operand, match_all):
        if key != 'scope_before_cursor':
            return None
        if operator not in (sublime.OP_EQUAL, sublime.OP_NOT_EQUAL):
            return None
        
        match = False
        for sel in view.sel():
            match = view.match_selector(max(0, sel.end() - 1), operand)
            if operator == sublime.OP_NOT_EQUAL:
                match = not match
            if match != match_all:
                break
        return match
2 Likes

#3

Working perfectly, Thanks!

0 Likes

#4

@addons_zz I just fixed a “small” bug, that would make ST hang if you are typing in a blank file :wink:

i.e. open a new tab, set syntax to SQL and type select and press Space and it would make ST unresponsive - oops!

In other news, it is a shame that plugins can do that…

1 Like

#5

Thanks for fixing it. I opened a issue on for kinda thing on the core:

  1. https://github.com/SublimeTextIssues/Core/issues/1463
0 Likes

#6

I released a new version 1.1:

"""
Automatically set all SQL keywords to upper case after the space keystroke.
Originally created by `kingkeith` on `https://forum.sublimetext.com/t/automatically-set-sql-keywords-to-upper-case/23760`.

Change log:

1.1 - Added multiple scopes support for the SQL's `support` scope on the `sublime.keymap` file.
1.0 - Initial release by `kingkeith`.
"""

import sublime
import sublime_plugin


class UpperCasePreviousItemAndInsertSpaceCommand( sublime_plugin.TextCommand ):
    """
    Called each time the space is pressed to process the preceding keyword.
    Set the space key bind to:

    { "keys": [" "], "command": "upper_case_previous_item_and_insert_space",
        "context":
        [
            { "key": "selector", "operator": "equal", "operand": "source.sql", "match_all": true },
            { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true },
            { "key": "scope_before_cursor", "operator": "equal", "operand": ["keyword", "support"], "match_all": true },
        ]
    },

    This class extends the `sublime_plugin.TextCommand` class.
    """

    def run( self, edit ):

        # can't use `view.sel()[:]` because it gives an error `TypeError: an integer is required`
        selections   = [ cursor for cursor in self.view.sel() ]
        to_uppercase = []

        for sel in selections:

            if sel.end() > 0:
                if self.view.substr( sel.end() - 1 ) == '(':
                    offset = 2
                else:
                    offset = 1

                #prev_item = self.view.extract_scope( sel.end() - offset )
                scope = self.view.scope_name( sel.end() - offset )
                begin = sel.end() - offset

                while self.view.scope_name( begin ) == scope and begin > 0:
                    begin -= 1

                prev_item = sublime.Region( begin, sel.end() )
                to_uppercase.append( prev_item )

        self.view.sel().clear()
        self.view.sel().add_all( to_uppercase )
        self.view.run_command( 'upper_case' )

        self.view.sel().clear()
        self.view.sel().add_all( selections )
        self.view.run_command( 'insert', { 'characters': ' ' } )


class ScopeBeforeCursorEventListener( sublime_plugin.EventListener ):
    """
    Event listener used on the key binding `scope_before_cursor` available on the class's
    `UpperCasePreviousItemAndInsertSpaceCommand` documentation.
    """

    def on_query_context( self, view, key, operator, operand, match_all ):

        if key != 'scope_before_cursor':
            return None

        if operator not in ( sublime.OP_EQUAL, sublime.OP_NOT_EQUAL ):
            return None

        match = False

        for sel in view.sel():
            #print( max(0, sel.end() - 1) )

            if view.substr(sel.end() - 1) == '(':
                offset = 2
            else:
                offset = 1

            for op in operand:
                match = view.match_selector( max(0, sel.end() - offset ), op )
                #print( 'On 1º match: ', match )
                
                if match:
                    break

            if operator == sublime.OP_NOT_EQUAL:
                match = not match

            if match != match_all:
                break

        #print( 'match: ', match )
        return match
0 Likes

#7

the normal way of specifying multiple scopes is just using a scope selector, i.e. in the keybindings:

{ "key": "scope_before_cursor", "operator": "equal", "operand": "keyword | support", "match_all": true },

that way, the plugin code doesn’t even have to change, and it doesn’t change the semantics of the operand such that an array is needed/expected by the plugin code.

more info on scope selectors here:

1 Like