Christopher Cro
Giga Expert

Intro

Did you know that the main ServiceNow script editor is built using the open source CodeMirror project? Did you also know that these editors expose the full CodeMirror configuration and customization API to the browser, enabling the installation of themes and addons (including Vim mode)? If that intrigues you, read on!

How to use userscripts

First, how would we even begin with making personalized changes to the editor? Wouldn't we have to worry about messing up other people's workflow? Thanks to the magic of userscripts, that's not an issue! Userscripts are a special type of script file that you install on your local browser via a helper extension. By making our modifications via userscript, we can run client-side scripting without having to worry about affecting the ServiceNow instance or anyone else using it.

Userscripts are just normal client-side Javascript files with a special block of comments at the top. To use a userscript in your browser, you first need to install a userscript helper extension. There are many such extensions available for all major browsers, just search for "userscripts" on your browser's extension store! (if unsure, I personally advocate for using the ViolentMonkey extension.)

Worth noting for those willing to dive deeper: all userscript extensions provide special helper functions that userscripts may take advantage of, the most universal set of functions is the GreaseMonkey v3 API, documented here.

A userscript for enabling Vim mode

On to the main event: immediately below is my own personal userscript which I use for enabling Vim mode. This particular script also happens to modify the editor so that it expands to the internal content. If all you want is Vim mode, you can simply remove lines 22-25 AND line 5 to get rid of the resizing.

// ==UserScript==
// @name        SNOW CodeMirror Vim activator & embiggener
// @description Enables vim mode for ServiceNow in most script editor fields. Also englarges the field!
// @namespace   https://gist.githubusercontent.com/chaorace/dc49a18f876a9814c8b27d637bfb4f44/raw/sn-vimify.user.js
// @updateURL   https://gist.githubusercontent.com/chaorace/dc49a18f876a9814c8b27d637bfb4f44/raw/sn-vimify.user.js
// @supportURL  https://gist.github.com/chaorace/dc49a18f876a9814c8b27d637bfb4f44
// @include     http://*.service-now.com/*
// @include     https://*.service-now.com/*
// @require     https://codemirror.net/keymap/vim.js
// @run-at      document-idle
// ==/UserScript==
(function() {
    // NOTE: This userscript will throw harmless errors if a given frame doesn't have a CodeMirror editor
    // This is something that happens during the loading phase for @require, so we can't suppress it
    if (typeof g_glideEditorArray !== 'undefined' && Array.isArray(g_glideEditorArray)){
        // Apply the below modifications to each major CodeMirror editor
        g_glideEditorArray.forEach((cm) => {
            // Drop the escape keybind that advances to the next field (obviously, we need escape for vim mode!)
            cm.editor.state.keyMaps = cm.editor.state.keyMaps.filter((x) => !x.Esc)
            // Actually set the keymap to the one previously loaded from our @require
            cm.editor.options.keyMap = 'vim'
            // An infinite viewport margin lets the editor grow with content
            cm.editor.options.viewportMargin = Infinity
            // Set the max height to 2/3rds of a screen height
            cm.editor.getScrollerElement().style.maxHeight = '66vh'
        })
    }
})()

See also: Github

A crash course to making your own editor mods

The script above can be modified to make just about any customization that the CodeMirror API allows for. As you can see, we are looping over g_glideEditorArray (which holds all of the frame's code editor objects) and applying our customizer function to each individual editor instance. See below for a boilerplate script that you can use as a basis for your own customizations:

// ==UserScript==
// @name        My ServiceNow editor mod
// @include     http://*.service-now.com/*
// @include     https://*.service-now.com/*
// @run-at      document-idle
// ==/UserScript==
(function() {
    if (typeof g_glideEditorArray !== 'undefined' && Array.isArray(g_glideEditorArray)){
        // Apply the below modifications to each major CodeMirror editor
        g_glideEditorArray.forEach((cm) => {
            // Your code goes here. For example, this changes the base keymap:
            // cm.editor.options.keyMap = 'emacsy'
        })
    }
})()

Example: downloading addons

If you want to load an addon (officially supported addons), you will need to use an @require line to import it via URL. Keep in mind that doing this will generate harmless errors on any pages that don't actually have a code editor on them.

// ==UserScript==
// @name        Addon loading example
// @include     http://*.service-now.com/*
// @include     https://*.service-now.com/*
// @require     https://codemirror.net/addon/hint/anyword-hint.js
// @run-at      document-idle
// ==/UserScript==
(function() {
    // NOTE: This userscript will throw harmless errors if a given frame doesn't have a CodeMirror editor
    // This is something that happens during the loading phase for @require, so we can't suppress it
    if (typeof g_glideEditorArray !== 'undefined' && Array.isArray(g_glideEditorArray)){
        g_glideEditorArray.forEach((cm) => {
            // Most addons will require some sort of configuration to work, for example:
            // CodeMirror.hint.javascript = CodeMirror.hint.anyword

            /* This particular example happens to configure the global CodeMirror object
             * Some situations will require configuring individual editors instead!
             * See the next theme-based example to see what that looks like
             */
        })
    }
})()

Example: adding themes

If you want load a theme, you will first need to pull in that theme's CSS file. There are dozens of official themes available here! Once you've picked a theme to use, you will need to use a special userscript method called GM_addStyle to load the theme CSS. Just remember that special methods like GM_addStyle need to be activated first with a special @grant line! The below example loads the gruvbox-dark theme, but you can very easily tweak it to load any of the previously linked official themes (See line 12, themeName variable):

// ==UserScript==
// @name        My ServiceNow editor theme installer
// @grant       GM_addStyle
// @include     http://*.service-now.com/*
// @include     https://*.service-now.com/*
// @run-at      document-idle
// ==/UserScript==
(function() {
    if (typeof g_glideEditorArray !== 'undefined' && Array.isArray(g_glideEditorArray)){
        g_glideEditorArray.forEach((cm) => {
            // Change this to the name of your desired theme (gruvbox is pretty great!)
            const themeName = 'gruvbox-dark'

            // Insert the CSS import statement using GM_addStyle
            // NOTE: Don't forget to enable GM_addStyle using @grant! (line 3)
            GM_addStyle('@import "https://codemirror.net/theme/' + themeName + '.css"'

            // Configure each individual editor to use the loaded theme
            cm.editor.options.theme = themeName
        })
    }
})()

Configuring ALL editors on your instance

Let's say you don't want your customizations to apply only to your own browser... that's actually possible! I wouldn't particularly recommend trying something like changing everyone's editors to Vim mode, but you certainly could if you wanted to! (You might say that it would take a particularly... eVil person to do something like that.) More realistically, you might do something like this if you wanted to add some additional shortcut keys for everyone to use, for example. The trick to doing this is using a Global UI Script.

Caution: When I say "Global", I mean two things! One: the UI Script needs to be created in the Global scope. Two: The UI Script needs to have the "Global" checkbox activated. Doing both of these things will cause the script to load on nearly ALL internal pages (i.e.: almost everywhere that isn't a dashboard/report/portal). With that being said, be extremely wary of performance issues! If a global UI Script would slow things down by, let's say... 3 seconds, that would slow down almost every single page by 3 seconds, because it runs everywhere!

Since this script runs in pretty much the same context as a userscript would, the actual code is basically identical (though, GM_* functions will NOT be available!) See the below revised boilerplate for use in global UI Scripts (modified from the secondmost example, above):

/* NOTE: We no longer have any special "@" comments, because this is not a userscript
 * Essentially everything else works exactly the same!
 * Just remember that those special "GM_*" functions will not be available!
 * We are also avoiding ES6 features, since others might be using old browsers
 */

(function() {
    if (typeof g_glideEditorArray !== 'undefined'){
        // Apply the below modifications to each major CodeMirror editor
        g_glideEditorArray.forEach(function(cm){
            // Your code goes here. For example, this changes the base keymap:
            // cm.editor.options.keyMap = 'emacsy';
        });
    }
})();
Version history
Last update:
‎12-04-2020 11:23 AM
Updated by: