From 49c2c07d0c271907746f76e3c29406476fb3186c Mon Sep 17 00:00:00 2001 From: Eric Schneider <37347760+eric-schneider@users.noreply.github.com> Date: Thu, 24 Sep 2026 00:57:01 -0500 Subject: [PATCH 01/10] Normalize [source,language-] and add protobuf --- src/js/00-normalize-source-lang.js | 23 +++++++++++++++++++++++ src/js/vendor/highlight.bundle.js | 1 + 2 files changed, 24 insertions(+) create mode 100644 src/js/00-normalize-source-lang.js diff --git a/src/js/00-normalize-source-lang.js b/src/js/00-normalize-source-lang.js new file mode 100644 index 0000000..146cd2c --- /dev/null +++ b/src/js/00-normalize-source-lang.js @@ -0,0 +1,23 @@ +;(function () { + 'use strict' + + // An AsciiDoc source block written as [source,language-bash] makes Asciidoctor emit + // class="language-language-bash" and data-lang="language-bash". highlight.js then finds no + // matching language and leaves the block plain, and the source toolbox shows "language-bash". + // Strip the redundant prefix before the toolbox (06-copy-to-clipboard.js) and the + // highlight.js bundle (loaded async after this script) read the language. + var REDUNDANT_PREFIX_RX = /^(?:language-)+/i + + ;[].slice.call(document.querySelectorAll('pre.highlight > code[data-lang]')).forEach(function (code) { + var lang = code.dataset.lang + if (!REDUNDANT_PREFIX_RX.test(lang)) return + var normalized = lang.replace(REDUNDANT_PREFIX_RX, '') + code.classList.remove('language-' + lang) + if (normalized) { + code.classList.add('language-' + normalized) + code.dataset.lang = normalized + } else { + delete code.dataset.lang + } + }) +})() diff --git a/src/js/vendor/highlight.bundle.js b/src/js/vendor/highlight.bundle.js index eea40cd..dd1c048 100644 --- a/src/js/vendor/highlight.bundle.js +++ b/src/js/vendor/highlight.bundle.js @@ -32,6 +32,7 @@ hljs.registerLanguage('perl', require('highlight.js/lib/languages/perl')) hljs.registerLanguage('php', require('highlight.js/lib/languages/php')) hljs.registerLanguage('properties', require('highlight.js/lib/languages/properties')) + hljs.registerLanguage('protobuf', require('highlight.js/lib/languages/protobuf')) hljs.registerLanguage('puppet', require('highlight.js/lib/languages/puppet')) hljs.registerLanguage('python', require('highlight.js/lib/languages/python')) hljs.registerLanguage('ruby', require('highlight.js/lib/languages/ruby')) From e218ef048d2fbe557d0076bfe9ca6dd89aeaf11d Mon Sep 17 00:00:00 2001 From: Eric Schneider <37347760+eric-schneider@users.noreply.github.com> Date: Thu, 24 Sep 2026 11:45:02 -0500 Subject: [PATCH 02/10] Highlight CQL and GraphQL code blocks - Register highlightjs-cql 1.0.0 for cql and cqlsh blocks, replacing the SQL stand-in. It colors CQL by role (types, names, setting names, functions, bind markers, cqlsh prompts and commands) for Apache Cassandra, DSE, HCD, and Astra DB. - Register GraphQL (graphql, gql), ported from highlight.js 11.12.0, which highlight.js 9 lacks. Output matches highlight.js 11 on all 19 GraphQL blocks in the docs. - Add a license notice for the bundled highlighting code to the top of highlight.bundle.js. - Style placeholders () in italics instead of the error color. - Keep lint and format off src/js/vendor/languages, which holds grammars maintained elsewhere. --- gulpfile.js | 4 +- src/css/highlight.css | 5 +- src/js/vendor/highlight.bundle.js | 10 +- src/js/vendor/languages/README.md | 16 + src/js/vendor/languages/cql.js | 717 +++++++++++++++++++++++++++++ src/js/vendor/languages/graphql.js | 85 ++++ 6 files changed, 833 insertions(+), 4 deletions(-) create mode 100644 src/js/vendor/languages/README.md create mode 100644 src/js/vendor/languages/cql.js create mode 100644 src/js/vendor/languages/graphql.js diff --git a/gulpfile.js b/gulpfile.js index 47e1aa3..7359d64 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -18,7 +18,9 @@ const task = require('./gulp.d/tasks') const glob = { all: [srcDir, previewSrcDir], css: `${srcDir}/css/**/*.css`, - js: ['gulpfile.js', 'gulp.d/**/*.js', `${srcDir}/{helpers,js}/**/*.js`], + // src/js/vendor/languages holds grammars maintained elsewhere (see its README); they keep + // their upstream formatting, so lint and format skip them. + js: ['gulpfile.js', 'gulp.d/**/*.js', `${srcDir}/{helpers,js}/**/*.js`, `!${srcDir}/js/vendor/languages/**`], } const cleanTask = createTask({ diff --git a/src/css/highlight.css b/src/css/highlight.css index 035a4d8..3ca7e10 100644 --- a/src/css/highlight.css +++ b/src/css/highlight.css @@ -10,8 +10,11 @@ color: var(--ds-failure-main); } +/* Placeholders such as in CQL synopses: italic, like variables in prose, + rather than the error color. */ .hljs-template-variable { - color: var(--ds-failure-main); + color: var(--ds-text-secondary); + font-style: italic; } .hljs-tag { diff --git a/src/js/vendor/highlight.bundle.js b/src/js/vendor/highlight.bundle.js index dd1c048..33e31ea 100644 --- a/src/js/vendor/highlight.bundle.js +++ b/src/js/vendor/highlight.bundle.js @@ -1,3 +1,9 @@ +/*! Syntax highlighting in this bundle: + * highlight.js 9.18.3 | BSD-3-Clause | Copyright (c) 2006, Ivan Sagalaev | https://github.com/highlightjs/highlight.js + * GraphQL grammar ported from highlight.js 11.12.0 | BSD-3-Clause | Copyright (c) 2006, Ivan Sagalaev + * highlightjs-curl 1.3.0 | Apache-2.0 | John Foster | https://github.com/highlightjs/highlightjs-curl + * highlightjs-cql 1.0.0 | Apache-2.0 | https://github.com/eric-schneider/highlightjs-cql + */ ;(function () { 'use strict' @@ -17,6 +23,7 @@ hljs.registerLanguage('dockerfile', require('highlight.js/lib/languages/dockerfile')) hljs.registerLanguage('elixir', require('highlight.js/lib/languages/elixir')) hljs.registerLanguage('go', require('highlight.js/lib/languages/go')) + hljs.registerLanguage('graphql', require('./languages/graphql.js')) hljs.registerLanguage('groovy', require('highlight.js/lib/languages/groovy')) hljs.registerLanguage('haskell', require('highlight.js/lib/languages/haskell')) hljs.registerLanguage('ini', ini) @@ -40,8 +47,7 @@ hljs.registerLanguage('scala', require('highlight.js/lib/languages/scala')) hljs.registerLanguage('shell', require('highlight.js/lib/languages/shell')) hljs.registerLanguage('sql', sql) - // Use SQL highlighter for CQL - hljs.registerLanguage('cql', sql) + hljs.registerLanguage('cql', require('./languages/cql.js')) hljs.registerLanguage('swift', require('highlight.js/lib/languages/swift')) hljs.registerLanguage('toml', ini) hljs.registerLanguage('typescript', require('highlight.js/lib/languages/typescript')) diff --git a/src/js/vendor/languages/README.md b/src/js/vendor/languages/README.md new file mode 100644 index 0000000..99d19c6 --- /dev/null +++ b/src/js/vendor/languages/README.md @@ -0,0 +1,16 @@ +# Syntax highlighting grammars + +`highlight.bundle.js` registers these grammars with highlight.js 9.18.3, alongside the ones highlight.js ships. +They are maintained outside this repository, so lint and format skip this folder (see `glob.js` in `gulpfile.js`). + +| File | Language | Source | License | +|---|---|---|---| +| `cql.js` | CQL and cqlsh (`cql`, `cqlsh`) | `dist/cql.cjs` of [highlightjs-cql](https://github.com/eric-schneider/highlightjs-cql) 1.0.0 | Apache-2.0 | +| `graphql.js` | GraphQL (`graphql`, `gql`) | Ported from highlight.js 11.12.0 `src/languages/graphql.js`, which highlight.js 9 lacks | BSD-3-Clause | + +The license notice at the top of `highlight.bundle.js` survives minification, so it appears in the published `js/vendor/highlight.bundle.js`. + +## Updating + +- **CQL.** Copy `dist/cql.cjs` from a highlightjs-cql release to `cql.js`, and update its version in the notice at the top of `highlight.bundle.js`. +- **GraphQL.** If you move to highlight.js 11, remove `graphql.js` and register `highlight.js/lib/languages/graphql` instead. To refresh the port from a newer highlight.js release, apply the changes its header lists. diff --git a/src/js/vendor/languages/cql.js b/src/js/vendor/languages/cql.js new file mode 100644 index 0000000..d4827e8 --- /dev/null +++ b/src/js/vendor/languages/cql.js @@ -0,0 +1,717 @@ +/*! highlightjs-cql 1.0.0 | CQL for highlight.js 9.18, 10.7, and 11.x | Apache-2.0 | https://github.com/eric-schneider/highlightjs-cql */ +'use strict'; + +// GENERATED by tools/generate-vocabulary.mjs from data/*.json. Do not edit by hand. +// Sources: Apache Cassandra 1.2, 2.0, 2.1, 2.2, 3.0, 3.11, 4.0, 4.1, 5.0, 6.0, trunk; DataStax fork +// (HCD 1.1.6, 1.2.7, 2.0.7); DataStax Enterprise 5.1.51, 6.8.65, 6.9.25. +var VOCABULARY = { + keywords: 'access active add aggregate aggregates all allow alter and ann any apply as asc authentication authorize backup backups batch begin between bm25 by call called calls cancel cast check cidrs clean cluster clustering column columnfamilies columnfamily columns comment comments commit compact config configuration configurations contains count counter create custom datacenters default delete desc describe deterministic distinct drop dropped edge end entries execute exists field filtering finalfunc for force from full function functions generated geo_distance grant group hashed identity if in index indexes indices initcond input insert internal internals into is json kerberos key keys keyspace keyspaces label labels language ldap let like limit list login masked materialized maxwritetime mbean mbeans method modify monotonic nologin norecursive nosuperuser not object of offset on only options or order partition password per permission permissions primary profiles proxy.execute proxy.login rebuild record reload remote rename replace restore restrict returns revoke role roles rows run schema scheme schemes search security select select_masked set sfunc static stdin stdout storage store stores stype submission superuser superusers table tables then timestamp to token transaction trigger truncate ttl type types unlogged unmask unrestrict unset update use user users using values verify vertex view where with without workpool writetime', + reserved: [ + 'add', + 'allow', + 'alter', + 'and', + 'apply', + 'asc', + 'authorize', + 'batch', + 'begin', + 'by', + 'columnfamily', + 'create', + 'delete', + 'desc', + 'describe', + 'drop', + 'entries', + 'execute', + 'from', + 'full', + 'grant', + 'if', + 'in', + 'index', + 'infinity', + 'insert', + 'into', + 'is', + 'keyspace', + 'limit', + 'materialized', + 'modify', + 'nan', + 'norecursive', + 'not', + 'null', + 'of', + 'on', + 'or', + 'order', + 'primary', + 'rename', + 'revoke', + 'schema', + 'select', + 'set', + 'table', + 'to', + 'token', + 'truncate', + 'unlogged', + 'update', + 'use', + 'using', + 'view', + 'where', + 'with' + ], + types: [ + 'ascii', + 'bigint', + 'blob', + 'boolean', + 'counter', + 'date', + 'decimal', + 'double', + 'duration', + 'float', + 'inet', + 'int', + 'smallint', + 'text', + 'time', + 'timestamp', + 'timeuuid', + 'tinyint', + 'uuid', + 'varchar', + 'varint' + ], + typeConstructors: [ + 'frozen', + 'list', + 'map', + 'set', + 'tuple', + 'vector' + ], + literals: 'false infinity nan null true', + functions: [ + 'abs', + 'ascii_as_blob', + 'asciiasblob', + 'avg', + 'bigint_as_blob', + 'bigintasblob', + 'blob_as_ascii', + 'blob_as_bigint', + 'blob_as_boolean', + 'blob_as_counter', + 'blob_as_date', + 'blob_as_decimal', + 'blob_as_double', + 'blob_as_duration', + 'blob_as_empty', + 'blob_as_float', + 'blob_as_inet', + 'blob_as_int', + 'blob_as_smallint', + 'blob_as_text', + 'blob_as_time', + 'blob_as_timestamp', + 'blob_as_timeuuid', + 'blob_as_tinyint', + 'blob_as_uuid', + 'blob_as_varchar', + 'blob_as_varint', + 'blobasascii', + 'blobasbigint', + 'blobasboolean', + 'blobascounter', + 'blobasdate', + 'blobasdecimal', + 'blobasdouble', + 'blobasduration', + 'blobasempty', + 'blobasfloat', + 'blobasinet', + 'blobasint', + 'blobassmallint', + 'blobastext', + 'blobastime', + 'blobastimestamp', + 'blobastimeuuid', + 'blobastinyint', + 'blobasuuid', + 'blobasvarchar', + 'blobasvarint', + 'boolean_as_blob', + 'booleanasblob', + 'cast', + 'cast_as_ascii', + 'cast_as_bigint', + 'cast_as_date', + 'cast_as_decimal', + 'cast_as_double', + 'cast_as_float', + 'cast_as_int', + 'cast_as_smallint', + 'cast_as_text', + 'cast_as_timestamp', + 'cast_as_tinyint', + 'cast_as_varint', + 'castasascii', + 'castasbigint', + 'castasdate', + 'castasdecimal', + 'castasdouble', + 'castasfloat', + 'castasint', + 'castassmallint', + 'castastext', + 'castastimestamp', + 'castastinyint', + 'castasvarint', + 'collection_avg', + 'collection_count', + 'collection_max', + 'collection_min', + 'collection_sum', + 'count', + 'count_rows', + 'counter_as_blob', + 'counterasblob', + 'countrows', + 'current_date', + 'current_time', + 'current_timestamp', + 'current_timeuuid', + 'currentdate', + 'currenttime', + 'currenttimestamp', + 'currenttimeuuid', + 'date_as_blob', + 'dateasblob', + 'dateof', + 'decimal_as_blob', + 'decimalasblob', + 'double_as_blob', + 'doubleasblob', + 'duration_as_blob', + 'durationasblob', + 'empty_as_blob', + 'emptyasblob', + 'exp', + 'float_as_blob', + 'floatasblob', + 'floor', + 'format_bytes', + 'format_time', + 'from_json', + 'fromjson', + 'geo_distance', + 'inet_as_blob', + 'inetasblob', + 'int_as_blob', + 'intasblob', + 'json', + 'length', + 'log', + 'log10', + 'map_keys', + 'map_values', + 'mask_default', + 'mask_hash', + 'mask_inner', + 'mask_null', + 'mask_outer', + 'mask_replace', + 'max', + 'max_timeuuid', + 'maxtimeuuid', + 'maxwritetime', + 'min', + 'min_timeuuid', + 'mintimeuuid', + 'normalize_l2', + 'now', + 'octet_length', + 'random_float_vector', + 'regexp', + 'round', + 'sai_analyze', + 'similarity_cosine', + 'similarity_dot_product', + 'similarity_euclidean', + 'smallint_as_blob', + 'smallintasblob', + 'sum', + 'text_as_blob', + 'textasblob', + 'time_as_blob', + 'timeasblob', + 'timestamp_as_blob', + 'timestampasblob', + 'timeuuid_as_blob', + 'timeuuidasblob', + 'tinyint_as_blob', + 'tinyintasblob', + 'to_date', + 'to_json', + 'to_timestamp', + 'to_unix_timestamp', + 'todate', + 'tojson', + 'token', + 'totimestamp', + 'tounixtimestamp', + 'transformation_kind', + 'ttl', + 'unixtimestampof', + 'uuid', + 'uuid_as_blob', + 'uuidasblob', + 'varchar_as_blob', + 'varcharasblob', + 'varint_as_blob', + 'varintasblob', + 'writetime' + ], + consistencyLevels: [ + 'all', + 'any', + 'each_quorum', + 'local_one', + 'local_quorum', + 'local_serial', + 'node_local', + 'one', + 'quorum', + 'serial', + 'three', + 'two' + ], + cqlshLineCommands: [ + 'capture', + 'clear', + 'cls', + 'consistency', + 'debug', + 'elapsed', + 'exit', + 'expand', + 'help', + 'history', + 'login', + 'paging', + 'quit', + 'serial', + 'show', + 'source', + 'timing', + 'tracing' + ], + cqlshArguments: 'consistency host off on replicas session version', + roleOptions: [ + 'access', + 'generated', + 'hashed', + 'login', + 'password', + 'superuser' + ], + unreservedPermissions: [ + 'select_masked', + 'unmask' + ]}; + +/* +Language: CQL +Description: Cassandra Query Language and cqlsh commands for Apache Cassandra (1.2 through 6.0), DataStax Enterprise, Hyper-Converged Database (HCD), and Astra DB. +Author: Eric Schneider +Contributors: Therapon Skoteiniotis (original beta definition) +Website: https://cassandra.apache.org/doc/latest/cassandra/developing/cql/ +Category: database +*/ + + +/** @type LanguageFn */ +function cql(hljs) { + var V = VOCABULARY; + + // --- Version adapter --------------------------------------------------------------- + // highlight.js 11 renders sub-scopes such as title.function.invoke as + // "hljs-title function_ invoke__". Versions 9 and 10 would emit the dotted name as a + // single class no theme styles, so they get the parent scope ("hljs-title") instead. + // highlight.js 9 has no versionString, so a missing value means 9. + var MAJOR = parseInt(String(hljs.versionString || '9').split('.')[0], 10) || 9; + var MODERN = MAJOR >= 11; + function scope(name) { + return MODERN ? name : name.split('.')[0]; + } + + // A keyword is a word, or a dotted chain of words. Treating "ks.table" as one lexeme + // keeps qualified names such as system_schema.tables from highlighting "tables", and + // lets DataStax Enterprise permissions such as PROXY.LOGIN be keywords. + var LEXEME = /[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*/; + + // Version 9 and 10 read the keyword pattern from mode.lexemes; 11 reads keywords.$pattern + // and ignores lexemes. Every mode that has keywords goes through this helper. + function withKeywords(mode, keywords) { + var copy = {}; + for (var k in keywords) { + if (Object.prototype.hasOwnProperty.call(keywords, k)) copy[k] = keywords[k]; + } + if (MODERN) copy.$pattern = LEXEME; + else mode.lexemes = LEXEME; + mode.keywords = copy; + return mode; + } + + function escapeRegex(s) { + return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); + } + function either(words) { + return '(?:' + words.map(escapeRegex).join('|') + ')'; + } + + var IDENT = '[A-Za-z][A-Za-z0-9_]*'; + var TYPES = either(V.types); + var CONSTRUCTORS = either(V.typeConstructors); + var TYPE_START = '(?:' + CONSTRUCTORS + '\\s*<|' + TYPES + '\\b)'; + + // Words that can never be the name in "name type" or "name = value": Cassandra's + // reserved words, plus words that are keywords in exactly those positions. + var DSE_STATEMENT_WORDS = ['backup', 'backups', 'restore', 'store', 'stores', 'search', + 'workpool', 'submission', 'remote', 'object', 'call', 'calls', 'scheme', 'schemes', + 'configuration', 'configurations', 'profiles', 'columns', 'cancel', 'clean', 'verify', + 'run', 'reload', 'rebuild', 'commit', 'restrict', 'unrestrict', 'rows', 'indices', 'config']; + var NOT_A_NAME_WORDS = V.reserved.concat(V.literals.split(' '), V.unreservedPermissions, + DSE_STATEMENT_WORDS, ['as', 'returns', 'stype', 'static', 'record', 'access', 'default', 'unset', + 'keyspaces', 'tables', 'functions', 'aggregates', 'mbeans', 'roles']); + var NOT_A_NAME = '(?!' + either(NOT_A_NAME_WORDS) + '\\b)'; + // Inside WITH clauses, role options (PASSWORD = '...', LOGIN = true) are keywords. NULL + // stays available as a name there: cqlsh COPY has an option called NULL (NULL = '-'). + var NOT_AN_OPTION_NAME = '(?!' + either(NOT_A_NAME_WORDS.filter(function (w) { + return w !== 'null'; + }).concat(V.roleOptions)) + '\\b)'; + + var KEYWORDS = { keyword: V.keywords, literal: V.literals }; + + // --- Literals ------------------------------------------------------------------------ + var DURATION_UNIT = '(?:y|mo|w|d|h|ms|us|\u00b5s|ns|m|s)'; + var NUMBER = { + className: 'number', + relevance: 0, + variants: [ + // UUID before integers: 123e4567-... must not split into 123e4567 and fragments. + { begin: /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/ }, + // Duration: 1h30m, 12mo, -2d, 3us (Lexer.g DURATION, DURATION_UNIT). + { begin: '-?\\b\\d+' + DURATION_UNIT + '(?:\\d+' + DURATION_UNIT + ')*\\b' }, + // Duration, ISO 8601: P0001-02-03T04:05:06, P1Y2M3DT4H, PT15M, P2W. + { begin: /-?\bP\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\b/ }, + { begin: /-?\bP(?:\d+W|(?=\d|T\d)(?:\d+Y)?(?:\d+M)?(?:\d+D)?(?:T(?=\d)(?:\d+H)?(?:\d+M)?(?:\d+S)?)?)\b/ }, + // Blob: 0x followed by zero or more hex digits (0x alone is an empty blob). + { begin: /\b0x[0-9a-f]*\b/ }, + // Integer and float, with the lexer's optional leading minus. "1..3" is a range. + { begin: /-?\b\d+(?:\.(?!\.)\d*)?(?:e[+-]?\d+)?\b/ } + ] + }; + var SPECIAL_FLOAT = { className: 'literal', begin: /-?\b(?:NaN|Infinity)\b/, relevance: 0 }; + + // Synopsis placeholders such as or , as used in reference + // documentation. Collection types (set) are matched before this rule. + var PLACEHOLDER = { + className: 'template-variable', + begin: /<[A-Za-z_][A-Za-z0-9_.:\/@-]*(?: [A-Za-z0-9_.:\/@-]+)*>/, + relevance: 0 + }; + + var STRING = { + className: 'string', + begin: "'", + end: "'", + contains: [{ begin: "''" }, PLACEHOLDER], + relevance: 0 + }; + // $$pg-style string$$, also used for function bodies. + var DOLLAR_STRING = { className: 'string', begin: /\$\$/, end: /\$\$/, relevance: 0 }; + // "Quoted names" keep their exact case and are never keywords. + var QUOTED_NAME = { begin: '"', end: '"', contains: [{ begin: '""' }], relevance: 0 }; + + var BIND_MARKER = { + className: 'variable', + relevance: 0, + variants: [ + { begin: /\?/ }, + // :name, but not a literal or function call after a UDT field name ({weight:null}). + { begin: /:(?!(?:true|false|null|nan|infinity)\b)[A-Za-z_][A-Za-z0-9_]*\b(?!\s*\()/ }, + { begin: /:"(?:[^"]|"")+"/ } + ] + }; + + var COMMENTS = [ + hljs.COMMENT('--', '$', { relevance: 0 }), + hljs.COMMENT('//', '$', { relevance: 0 }), + hljs.COMMENT('/\\*', '\\*/', { relevance: 0 }) + ]; + + // --- cqlsh ------------------------------------------------------------------------- + var PROMPT = { className: scope('meta.prompt'), begin: /cqlsh(?::[A-Za-z0-9_"]+)?>/, relevance: 0 }; + // Where a cqlsh command can start: a line start, after a semicolon, or after a prompt. + var STATEMENT_START = '(?:^[ \\t]*|;[ \\t]*|cqlsh(?::[A-Za-z0-9_"]+)?>[ \\t]*)'; + var LEVEL = { + className: 'literal', + begin: '\\b' + either(V.consistencyLevels) + '\\b(?!\\s+CONSISTENCY\\b)', + relevance: 0 + }; + // Shell commands such as CONSISTENCY QUORUM or TRACING ON, recognized only at the start + // of a statement so that a column called "source" or "history" stays a name. + var PROMPT_SOURCE = 'cqlsh(?::[A-Za-z0-9_"]+)?>'; + var COMMAND_WORD = either(V.cqlshLineCommands) + '\\b'; + var CQLSH_COMMAND = withKeywords({ + variants: [ + // At a line start or after a semicolon the matched text is consumed; highlight.js + // colors it with this mode's keywords. It must not be re-scanned: a mode that begins + // at a semicolon and ends on that same semicolon would restart there forever. + { begin: '^[ \\t]*' + COMMAND_WORD }, + { begin: ';[ \\t]*' + COMMAND_WORD }, + // After a prompt the text is re-scanned, so that the prompt gets its own scope. + { begin: PROMPT_SOURCE + '[ \\t]*' + COMMAND_WORD, returnBegin: true } + ], + end: /;|$/, + returnEnd: true, + relevance: 0, + contains: [PROMPT, STRING, PLACEHOLDER, LEVEL, NUMBER, { className: 'literal', begin: /\b(?:ON|OFF)\b/ }] + }, { keyword: V.cqlshLineCommands.join(' ') + ' ' + V.cqlshArguments }); + // COPY is a cqlsh command only at the start of a statement; the rest of the statement is + // CQL-like (TO 'file' WITH HEADER = true). The mode ends on the word COPY itself, which + // highlight.js colors with the mode's keywords. (A mode without an explicit end would end + // at the first position after a nested prompt, before reaching COPY.) + var COPY_COMMAND = withKeywords({ + begin: STATEMENT_START + 'COPY\\b', + returnBegin: true, + end: /\bCOPY\b/, + relevance: 0, + contains: [PROMPT] + }, { keyword: 'copy' }); + + // cqlsh query output pasted into examples: a separator row of dashes (with + between + // columns), the rows below it, and the "(3 rows)" footer stay plain text. + var RESULT_TABLE = { + relevance: 0, + // Each pattern has exactly one way to match a line ([^\n|]* stops at the first |, + // [ \t]* at the first visible character), so a long line cannot cause backtracking. + variants: [ + { begin: /^[^\n|]*\|[^\n]*(?=\n[ \t]*-+(?:\+-+)+[ \t]*$)/ }, + { begin: /^[ \t]*\S[^\n]*(?=\n[ \t]*-{3,}[ \t]*$)/ }, + { begin: /^[ \t]*-+(?:\+-+)+[ \t]*$/ } + ], + end: /^[ \t]*$|^\(\d+ rows?\)/ + }; + var ROW_COUNT = { begin: /^\(\d+ rows?\)[ \t]*$/, relevance: 0 }; + + // --- Types --------------------------------------------------------------------------- + // Type names are colored only where a type can appear, because every native type name + // (text, date, time, ...) is also a legal column name. + // Inside <...>, nested collection types come first, so frozen
in + // map> is not mistaken for an
placeholder. + var NESTED_GENERIC = { begin: '\\b' + CONSTRUCTORS + '\\s*<', end: />/, relevance: 0 }; + var ANGLES = { begin: //, relevance: 0 }; + var GENERIC_CONTENTS = [NESTED_GENERIC, PLACEHOLDER, NUMBER, QUOTED_NAME, ANGLES]; + NESTED_GENERIC.contains = GENERIC_CONTENTS; + ANGLES.contains = GENERIC_CONTENTS; + function genericType(endsParent) { + return { + className: 'type', + begin: '\\b' + CONSTRUCTORS + '\\s*<', + end: '>', + endsParent: endsParent, + relevance: 0, + contains: GENERIC_CONTENTS + }; + } + function typeWord(endsParent) { + return { className: 'type', begin: '\\b' + TYPES + '\\b', endsParent: endsParent, relevance: 0 }; + } + var TYPE_CONTENTS = [genericType(true), typeWord(true)]; + + // name type: a column, UDT field, or parameter definition (key text, date date). + var TYPED_NAME = { + relevance: 0, + variants: [ + { begin: '\\b' + NOT_A_NAME + IDENT + '(?=\\s+' + TYPE_START + ')' }, + { begin: '"(?:[^"]|"")+"(?=\\s+' + TYPE_START + ')' } + ], + end: /[,;()]/, + returnEnd: true, + contains: TYPE_CONTENTS + }; + // name udt_name: since Cassandra 3.6 a user-defined type column need not be frozen + // (address address_type). Two plain words are a definition only when a definition's + // ending follows: a comma, ")", STATIC, PRIMARY KEY, MASKED WITH, or CHECK. SELECT JSON + // and SELECT DISTINCT are followed by names, never by a type. + var UDT_TYPED_NAME = { + begin: '\\b' + NOT_A_NAME + '(?!(?:JSON|DISTINCT)\\b)' + IDENT + '(?=\\s+(?!' + either(V.keywords.split(' ').concat(V.literals.split(' '))) + + '\\b)' + IDENT + '\\s*(?:[,)]|\\b(?:STATIC|PRIMARY|MASKED|CHECK)\\b))', + end: /[,;()]/, + returnEnd: true, + relevance: 0, + contains: [{ className: 'type', begin: '\\b' + IDENT, endsParent: true, relevance: 0 }] + }; + // RETURNS int, STYPE map, RETURNS address (any name here is a type, so user- + // defined types are colored too). RETURNS NULL ON NULL INPUT is not a type. + var TYPE_AFTER_KEYWORD = withKeywords({ + begin: /\b(?:RETURNS|STYPE)\s+(?!NULL\b)(?=[A-Za-z"])/, + returnBegin: true, + end: /[,;()]/, + returnEnd: true, + relevance: 0, + contains: TYPE_CONTENTS.concat([{ + className: 'type', + begin: '(?:\\b(?!(?:RETURNS|STYPE)\\b)|(?="))(?:' + IDENT + '\\.)?(?:' + IDENT + '|"(?:[^"]|"")+")', + endsParent: true, + relevance: 0 + }]) + }, { keyword: 'returns stype' }); + // CAST(value AS int) + var CAST_TYPE = withKeywords({ + begin: '\\bAS\\s+(?=' + TYPES + '\\s*\\))', + returnBegin: true, + end: /\)/, + returnEnd: true, + relevance: 0, + contains: TYPE_CONTENTS + }, { keyword: 'as' }); + + // --- Functions ----------------------------------------------------------------------- + // Calls to native functions (now(), writetime(col), similarity_cosine(v, ?), ...). + // Unknown names before "(" are left plain: they are usually tables (INSERT INTO t (...)). + var FUNCTION_CALL = { + className: scope('title.function.invoke'), + begin: '\\b' + either(V.functions) + '(?=\\s*\\()', + relevance: 0 + }; + // CREATE FUNCTION ks.name (a int, b text), DROP AGGREGATE name (int), ON FUNCTION f(int) + var SIGNATURE = { + begin: /\(/, + end: /\)/, + endsParent: true, + relevance: 0, + contains: [TYPED_NAME, genericType(false), typeWord(false), QUOTED_NAME, PLACEHOLDER].concat(COMMENTS) + }; + var FUNCTION_DECLARATION = withKeywords({ + begin: /\b(?:FUNCTION|AGGREGATE)\s+(?=(?:IF\s+(?:NOT\s+)?EXISTS\s+)?["A-Za-z<])/, + returnBegin: true, + end: /;|$/, + returnEnd: true, + relevance: 0, + contains: [ + PLACEHOLDER, + { + className: scope('title.function'), + begin: '\\b(?!(?:FUNCTION|AGGREGATE|IF|NOT|EXISTS)\\b)(?:' + IDENT + '\\.)?(?:' + IDENT + '|"(?:[^"]|"")+")', + relevance: 0 + }, + SIGNATURE + ] + }, KEYWORDS); + + // --- Names in positions where keywords cannot appear ----------------------------------- + // Many CQL keywords are unreserved: key, type, status, comment, and every native type + // can name a column. A word right before a comparison, a list separator, FROM, AS, ASC, + // DESC, or a DataStax Enterprise search-index option block ({ ... }) is a name, so it is + // not colored as a keyword (WHERE key = 1; SELECT type, date; COLUMNS comment { ... }). + var RELATION = '(?=\\s*(?:!=|<=|>=|=|<(?![A-Za-z_])|>|:(?![A-Za-z_":])|\\b(?:IN|CONTAINS|LIKE|IS|BETWEEN|NOT)\\b))'; + var NAME_BEFORE_RELATION = { begin: '\\b' + NOT_A_NAME + IDENT + RELATION, relevance: 0 }; + var NAME_BEFORE_RELATION_IN_WITH = { begin: '\\b' + NOT_AN_OPTION_NAME + IDENT + RELATION, relevance: 0 }; + var NAME_BEFORE_DELIMITER = { + relevance: 0, + variants: [ + { begin: '\\b' + NOT_A_NAME + IDENT + '(?=\\s*[,)]|\\s+(?:FROM|ASC|DESC|AS)\\b)' }, + // A name before { is a DataStax Enterprise search-index column; ACCESS TO + // DATACENTERS {...} and CONFIG {...} are keywords. + { + begin: '\\b(?!' + either(NOT_A_NAME_WORDS.concat(['datacenters', 'cidrs', 'config', 'options', + 'contains', 'key'])) + + '\\b)' + IDENT + '(?=\\s*\\{)' + } + ] + }; + // The object named right after TABLE, FROM, INTO, ON, and similar words is a name even + // when the word is a keyword elsewhere: CREATE TABLE users, INSERT INTO user, KEYSPACE store. + // Each keyword lists the words that may follow it as syntax instead (FROM STDIN, ON ROLE). + var NAME_PART = '(?:' + IDENT + '|"(?:[^"]|"")+")'; + var NOT_OBJECT_BASE = V.reserved.concat(V.literals.split(' '), ['if', 'not', 'exists', 'all']); + function objectAfter(keywords, notObjects) { + return { + begin: '\\b' + either(keywords) + '\\s+(?:IF\\s+(?:NOT\\s+)?EXISTS\\s+)?(?!' + + either(NOT_OBJECT_BASE.concat(notObjects)) + '\\b)(?=["A-Za-z])' + }; + } + // DataStax Enterprise objects named by a keyword: FROM BACKUP b, ON STORE s, ON REMOTE OBJECT. + var DSE_OBJECT_WORDS = ['backup', 'backups', 'store', 'stores', 'configuration', 'configurations', + 'remote', 'any', 'workpool', 'submission', 'search', 'internal', 'ldap', 'kerberos', 'scheme', 'schemes']; + var OBJECT_NAME = withKeywords({ + variants: [ + objectAfter(['from'], ['stdin', 'cidrs'].concat(DSE_OBJECT_WORDS)), + objectAfter(['index'], ['schema', 'config']), + objectAfter(['truncate'], ['table']), + objectAfter(['on'], ['table', 'keyspace', 'keyspaces', 'tables', 'index', 'type', 'view', + 'materialized', 'column', 'function', 'functions', 'aggregate', 'aggregates', 'role', 'roles', + 'mbean', 'mbeans', 'resource', 'user', 'trigger'].concat(DSE_OBJECT_WORDS)), + objectAfter(['table', 'into', 'update', 'keyspace', 'use', 'view', 'type', 'user'], []) + ], + end: /[;(]|$/, + returnEnd: true, + relevance: 0, + contains: [{ begin: NAME_PART + '(?:\\.' + NAME_PART + ')?', endsParent: true, relevance: 0 }] + }, KEYWORDS); + // DataStax Enterprise: DESCRIBE PENDING SEARCH INDEX is parsed outside the grammar, so + // PENDING is a keyword only in that phrase. + var PENDING = { className: 'keyword', begin: /\bPENDING(?=\s+SEARCH\s+INDEX\b)/, relevance: 0 }; + // KEY is unreserved (a column may be called key), but PRIMARY KEY is always syntax. + var PRIMARY_KEY = { className: 'keyword', begin: /\bPRIMARY\s+KEY\b/, relevance: 0 }; + var ELLIPSIS = { begin: /\.\.\./, relevance: 0 }; + var OPERATOR = { className: 'operator', begin: /\+=|-=|!=|<=|>=|\.\.|[-+*\/%=<>]/, relevance: 0 }; + + var COMMON = [ + STRING, DOLLAR_STRING, + TYPED_NAME, UDT_TYPED_NAME, QUOTED_NAME, + PLACEHOLDER, BIND_MARKER, SPECIAL_FLOAT, NUMBER, + FUNCTION_DECLARATION, TYPE_AFTER_KEYWORD, CAST_TYPE, genericType(false), + FUNCTION_CALL, OBJECT_NAME, PENDING, PRIMARY_KEY, NAME_BEFORE_DELIMITER, + ELLIPSIS, OPERATOR + ]; + + // WITH option = value AND option = value: table, keyspace, index, and COPY options are + // setting names (the attr scope), except role options such as PASSWORD and LOGIN. + var OPTION_NAME = { + className: 'attr', + begin: '\\b' + NOT_AN_OPTION_NAME + IDENT + '(?=\\s*=(?!=))', + relevance: 0 + }; + var STATEMENT_KEYWORD = '(?:SELECT|INSERT|UPDATE|DELETE|CREATE|ALTER|DROP|BEGIN|APPLY|GRANT|' + + 'REVOKE|USE|TRUNCATE|LIST|DESCRIBE|DESC|COPY|COMMIT|RESTRICT|UNRESTRICT)'; + var WITH_OPTIONS = withKeywords({ + begin: /\bWITH\b/, + returnBegin: true, + end: new RegExp(';|^[ \\t]*' + STATEMENT_KEYWORD + '\\b'), + returnEnd: true, + relevance: 0, + contains: COMMENTS.concat([OPTION_NAME, NAME_BEFORE_RELATION_IN_WITH], COMMON) + }, KEYWORDS); + + return withKeywords({ + name: 'CQL', + aliases: ['cqlsh'], + case_insensitive: true, + // CQL resembles SQL closely enough that automatic detection would claim SQL blocks. + disableAutodetect: true, + contains: [RESULT_TABLE, ROW_COUNT].concat(COMMENTS, [CQLSH_COMMAND, COPY_COMMAND, PROMPT, WITH_OPTIONS], + COMMON, [NAME_BEFORE_RELATION]) + }, KEYWORDS); +} + +module.exports = cql; diff --git a/src/js/vendor/languages/graphql.js b/src/js/vendor/languages/graphql.js new file mode 100644 index 0000000..0377070 --- /dev/null +++ b/src/js/vendor/languages/graphql.js @@ -0,0 +1,85 @@ +/* + * GraphQL grammar for highlight.js 9, ported from highlight.js 11.12.0 + * (src/languages/graphql.js; Language: GraphQL; Author: John Foster (GH jf990), and others). + * + * The port changes only what highlight.js 9 needs: `scope` becomes `className`, `match` + * becomes `begin`, keyword lists are strings, the name-before-colon pattern is written out + * instead of built with `hljs.regex`, and the two `illegal` patterns are combined into one. + * + * BSD 3-Clause License + * + * Copyright (c) 2006, Ivan Sagalaev. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +'use strict' + +module.exports = function graphql (hljs) { + return { + name: 'GraphQL', + aliases: ['gql'], + case_insensitive: true, + disableAutodetect: false, + keywords: { + keyword: 'query mutation subscription type input schema directive interface union scalar fragment enum on', + literal: 'true false null', + }, + contains: [ + hljs.HASH_COMMENT_MODE, + hljs.QUOTE_STRING_MODE, + hljs.NUMBER_MODE, + { + className: 'punctuation', + begin: /[.]{3}/, + relevance: 0, + }, + { + className: 'punctuation', + begin: /[\!\(\)\:\=\[\]\{\|\}]{1}/, // eslint-disable-line no-useless-escape + relevance: 0, + }, + { + className: 'variable', + begin: /\$/, + end: /\W/, + excludeEnd: true, + relevance: 0, + }, + { + className: 'meta', + begin: /@\w+/, + excludeEnd: true, + }, + { + className: 'symbol', + begin: /[_A-Za-z][_0-9A-Za-z]*(?=\s*:)/, + relevance: 0, + }, + ], + illegal: /[;<']|BEGIN/, + } +} From 1b5969090c49312825280fe709e188ef8ad789ba Mon Sep 17 00:00:00 2001 From: Eric Schneider <37347760+eric-schneider@users.noreply.github.com> Date: Thu, 24 Sep 2026 17:50:28 -0500 Subject: [PATCH 03/10] Refresh the syntax highlighting theme - Give each kind of token its own color: keywords purple, types cyan, functions and headings pink, keys and setting names indigo, strings green, numbers and literals orange, variables and placeholders amber, prompts and comments gray, deleted lines red. - Meet WCAG AA contrast (4.5:1) in both themes. The colors come from new --ds-code-* variables: shade 700 of each palette family in the light theme and shade 400 in the dark theme. Before, strings, numbers, types, literals, and function names were below 4.5:1 in the light theme (as low as 3.41:1), and variables and XML names were below it in the dark theme (3.96:1). - Stop using the error color for variables and markup, and stop coloring whole parameter lists. - Italicize comments. Placeholders stay italic. - Make cqlsh prompts unselectable, like console prompts. --- src/css/asciidoc/doc.css | 4 +- src/css/highlight.css | 144 +++++++++++++++++---------------------- src/css/vars/dark.css | 13 ++++ src/css/vars/light.css | 13 ++++ 4 files changed, 92 insertions(+), 82 deletions(-) diff --git a/src/css/asciidoc/doc.css b/src/css/asciidoc/doc.css index b3be67d..e9fed25 100644 --- a/src/css/asciidoc/doc.css +++ b/src/css/asciidoc/doc.css @@ -764,7 +764,9 @@ padding: var(--ds-space-2); } - .doc .language-console .hljs-meta { + /* Prompts (console $, cqlsh>) are not selected along with the commands after them. */ + .doc .language-console .hljs-meta, + .doc .language-cql .hljs-meta { user-select: none; } diff --git a/src/css/highlight.css b/src/css/highlight.css index 3ca7e10..7f3478e 100644 --- a/src/css/highlight.css +++ b/src/css/highlight.css @@ -1,112 +1,94 @@ -.hljs-comment { - color: var(--ds-text-tertiary); -} +/* Syntax highlighting for code blocks, for highlight.js 9 class names and the highlight.js 11 + sub-scopes that differ. Colors come from the --ds-code-* variables in vars/light.css and + vars/dark.css; each has at least 4.5:1 contrast (WCAG AA) on the code block background. + Punctuation, operators, parameters, and names stay in the text color. */ +.hljs-comment, .hljs-quote { - color: var(--ds-text-tertiary); -} - -.hljs-variable { - color: var(--ds-failure-main); -} - -/* Placeholders such as in CQL synopses: italic, like variables in prose, - rather than the error color. */ -.hljs-template-variable { - color: var(--ds-text-secondary); + color: var(--ds-code-comment); font-style: italic; } -.hljs-tag { - color: var(--ds-failure-main); -} - -.hljs-name { - color: var(--ds-failure-main); -} - -.hljs-selector-id { - color: var(--ds-failure-main); -} - -.hljs-selector-class { - color: var(--ds-cyan-main); -} - -.hljs-regexp { - color: var(--ds-cyan-main); -} - -.hljs-deletion { - color: var(--ds-failure-main); -} - -.hljs-number { - color: var(--ds-orange-main); -} - -.hljs-built_in { - color: var(--ds-teal-main); -} - -.hljs-builtin-name { - color: var(--ds-orange-main); +/* Prompts, preprocessor lines, annotations, markup brackets, and list bullets. */ +.hljs-meta, +.hljs-tag, +.hljs-bullet { + color: var(--ds-code-meta); } -.hljs-literal { - color: var(--ds-orange-main); +.hljs-keyword, +.hljs-selector-tag, +.hljs-name, +.hljs-doctag, +.hljs-meta-keyword, +.hljs-meta .hljs-keyword, +.hljs-variable.language_ { + color: var(--ds-code-keyword); } -.hljs-type { - color: var(--ds-orange-main); +.hljs-type, +.hljs-built_in, +.hljs-builtin-name, +.hljs-title.class_ { + color: var(--ds-code-type); } -.hljs-params { - color: var(--ds-orange-main); +/* Names being defined or called: functions, classes, headings. */ +.hljs-title, +.hljs-section, +.hljs-selector-id { + color: var(--ds-code-title); } -.hljs-meta { - color: var(--ds-orange-main); +.hljs-section { + font-weight: 600; } +/* Keys and setting names: JSON and YAML keys, XML attributes, CSS properties, options. */ +.hljs-attr, +.hljs-attribute, +.hljs-property, +.hljs-symbol, +.hljs-selector-attr, +.hljs-selector-class, +.hljs-selector-pseudo, .hljs-link { - color: var(--ds-orange-main); + color: var(--ds-code-attr); } -.hljs-attribute { - color: var(--ds-orange-main); -} - -.hljs-string { - color: var(--ds-orange-main); -} - -.hljs-symbol { - color: var(--ds-success-main); -} - -.hljs-bullet { - color: var(--ds-success-main); +.hljs-link { + text-decoration: underline; } +.hljs-string, +.hljs-regexp, +.hljs-meta-string, +.hljs-meta .hljs-string, .hljs-addition { - color: var(--ds-success-main); + color: var(--ds-code-string); } -.hljs-title { - color: var(--ds-cyan-main); +.hljs-number, +.hljs-literal, +.hljs-char.escape_, +.hljs-variable.constant_ { + color: var(--ds-code-number); } -.hljs-section { - color: var(--ds-cyan-main); +/* Values supplied by the reader or the environment: variables, bind markers, and + placeholders such as , which are also italic, like variables in prose. */ +.hljs-variable, +.hljs-template-variable, +.hljs-template-tag { + color: var(--ds-code-variable); } -.hljs-keyword { - color: var(--ds-primary-main); +.hljs-template-variable { + font-style: italic; } -.hljs-selector-tag { - color: var(--ds-primary-main); +.hljs-deletion { + color: var(--ds-code-deletion); } .hljs-emphasis { @@ -114,5 +96,5 @@ } .hljs-strong { - font-weight: 500; + font-weight: 600; } diff --git a/src/css/vars/dark.css b/src/css/vars/dark.css index 1abf405..3998441 100644 --- a/src/css/vars/dark.css +++ b/src/css/vars/dark.css @@ -98,6 +98,19 @@ --ds-warning-solid-bg: theme(colors.amber.500); --ds-warning-solid-color: theme(colors.black); --ds-warning-solid-hover-bg: theme(colors.amber.400); + + /* Code syntax highlighting (see highlight.css). Each color has at least 4.5:1 + contrast (WCAG AA) on --ds-background-level1, the code block background. */ + --ds-code-comment: theme(colors.zinc.400); + --ds-code-meta: theme(colors.zinc.400); + --ds-code-keyword: theme(colors.purple.400); + --ds-code-type: theme(colors.cyan.400); + --ds-code-title: theme(colors.pink.400); + --ds-code-attr: theme(colors.indigo.400); + --ds-code-string: theme(colors.emerald.400); + --ds-code-number: theme(colors.orange.400); + --ds-code-variable: theme(colors.amber.400); + --ds-code-deletion: theme(colors.red.400); } @mixin dark-inverse { diff --git a/src/css/vars/light.css b/src/css/vars/light.css index b117532..c9e2bb1 100644 --- a/src/css/vars/light.css +++ b/src/css/vars/light.css @@ -98,6 +98,19 @@ --ds-warning-solid-bg: theme(colors.amber.600); --ds-warning-solid-color: theme(colors.white); --ds-warning-solid-hover-bg: theme(colors.amber.700); + + /* Code syntax highlighting (see highlight.css). Each color has at least 4.5:1 + contrast (WCAG AA) on --ds-background-level1, the code block background. */ + --ds-code-comment: theme(colors.zinc.500); + --ds-code-meta: theme(colors.zinc.500); + --ds-code-keyword: theme(colors.purple.700); + --ds-code-type: theme(colors.cyan.700); + --ds-code-title: theme(colors.pink.700); + --ds-code-attr: theme(colors.indigo.700); + --ds-code-string: theme(colors.emerald.700); + --ds-code-number: theme(colors.orange.700); + --ds-code-variable: theme(colors.amber.700); + --ds-code-deletion: theme(colors.red.700); } @mixin light-inverse { From 896f9a043c211a0cc2f2bbb23229e08430d46082 Mon Sep 17 00:00:00 2001 From: Eric Schneider <37347760+eric-schneider@users.noreply.github.com> Date: Fri, 25 Sep 2026 10:33:34 -0500 Subject: [PATCH 04/10] Separate setting names from keywords in the syntax theme Setting names (such as REPLICATION in WITH REPLICATION = ...) were indigo, next to purple keywords: a color difference (CIEDE2000) of only 8.9 in the light theme, and harder still to tell apart in capital letters. They are now cyan, a difference of 28.9. Types and built-ins move from cyan to lime, which also keeps them far from the keywords they sit beside (TEXT PRIMARY KEY). Both colors still meet WCAG AA contrast in both themes. --- src/css/vars/dark.css | 4 ++-- src/css/vars/light.css | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/css/vars/dark.css b/src/css/vars/dark.css index 3998441..c8cee2b 100644 --- a/src/css/vars/dark.css +++ b/src/css/vars/dark.css @@ -104,9 +104,9 @@ --ds-code-comment: theme(colors.zinc.400); --ds-code-meta: theme(colors.zinc.400); --ds-code-keyword: theme(colors.purple.400); - --ds-code-type: theme(colors.cyan.400); + --ds-code-type: theme(colors.lime.400); --ds-code-title: theme(colors.pink.400); - --ds-code-attr: theme(colors.indigo.400); + --ds-code-attr: theme(colors.cyan.400); --ds-code-string: theme(colors.emerald.400); --ds-code-number: theme(colors.orange.400); --ds-code-variable: theme(colors.amber.400); diff --git a/src/css/vars/light.css b/src/css/vars/light.css index c9e2bb1..46e2d19 100644 --- a/src/css/vars/light.css +++ b/src/css/vars/light.css @@ -104,9 +104,9 @@ --ds-code-comment: theme(colors.zinc.500); --ds-code-meta: theme(colors.zinc.500); --ds-code-keyword: theme(colors.purple.700); - --ds-code-type: theme(colors.cyan.700); + --ds-code-type: theme(colors.lime.700); --ds-code-title: theme(colors.pink.700); - --ds-code-attr: theme(colors.indigo.700); + --ds-code-attr: theme(colors.cyan.700); --ds-code-string: theme(colors.emerald.700); --ds-code-number: theme(colors.orange.700); --ds-code-variable: theme(colors.amber.700); From ffd56c571691926aa903cb29b51d6a46a2ebfbea Mon Sep 17 00:00:00 2001 From: Eric Schneider <37347760+eric-schneider@users.noreply.github.com> Date: Fri, 25 Sep 2026 11:01:12 -0500 Subject: [PATCH 05/10] Add syntax highlighting samples to the UI preview The preview's code page had one CQL block and no GraphQL, YAML, or XML, so a reviewer of the UI preview couldn't see most of what this branch changes. Add a "Syntax highlighting samples" section with CQL statements, a CQL syntax summary with placeholders, a cqlsh session, GraphQL, YAML, and XML. Preview pages aren't part of the UI bundle. --- preview-src/asciidoc/code.adoc | 78 ++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/preview-src/asciidoc/code.adoc b/preview-src/asciidoc/code.adoc index b480156..b565a4a 100644 --- a/preview-src/asciidoc/code.adoc +++ b/preview-src/asciidoc/code.adoc @@ -81,6 +81,84 @@ public class CurrentDateTime { } ---- +=== Syntax highlighting samples + +These samples show how each kind of token is colored in the languages the docs use most. + +.CQL: schema and queries +[source,cql] +---- +CREATE TABLE IF NOT EXISTS store.orders ( + order_id uuid, + placed_at timestamp, + status text, + items list>>, + embedding vector, + PRIMARY KEY ((order_id), placed_at) +) WITH CLUSTERING ORDER BY (placed_at DESC) + AND comment = 'Orders by customer' + AND default_time_to_live = 0; + +SELECT order_id, writetime(status), toJson(items) FROM store.orders +WHERE order_id = 6ab09bec-e68e-48d9-a5f8-97e6fb4c9b47 + AND placed_at >= ? -- a bind marker +ORDER BY embedding ANN OF [0.1, 0.2, 0.3] LIMIT 5; +---- + +.CQL: syntax summary with placeholders +[source,cql] +---- +CREATE TABLE [ IF NOT EXISTS ] [.] + ( [ STATIC ] [ PRIMARY KEY ] [ , ... ] ) + [ WITH ] ; +---- + +.CQL: cqlsh session +[source,cql] +---- +cqlsh> CONSISTENCY LOCAL_QUORUM +cqlsh> USE store; +cqlsh:store> SELECT order_id, status FROM orders LIMIT 1; + + order_id | status +--------------------------------------+-------- + 6ab09bec-e68e-48d9-a5f8-97e6fb4c9b47 | NEW + +(1 rows) +---- + +.GraphQL +[source,graphql] +---- +query getOrder($id: Uuid!) { + orders(value: { order_id: $id }) @cql_column(name: "orders") { + values { order_id status } + } +} +---- + +.YAML +[source,yaml] +---- +# Audit logging in cassandra.yaml +audit_logging_options: + enabled: true + logger: + - class_name: BinAuditLogger + included_categories: QUERY, DDL, AUTH +---- + +.XML +[source,xml] +---- + + + org.apache.cassandra + java-driver-core + ${driver.version} + +---- + === Line wrapping .Line wrapping is turned off by default From 3896d7d217f0284738d0d87c9e71959669bfdda7 Mon Sep 17 00:00:00 2001 From: Eric Schneider <37347760+eric-schneider@users.noreply.github.com> Date: Fri, 25 Sep 2026 12:49:39 -0500 Subject: [PATCH 06/10] Remove the [source,language-*] normalization IBM Docs, where these docs are moving, reads the language name exactly as written, so a block labeled [source,language-cql] isn't highlighted there. Normalizing the label here would hide that problem from writers. Without the normalization, such blocks are plain on this site too, which shows that the label needs fixing. Protobuf highlighting, added in the same earlier commit, stays. --- src/js/00-normalize-source-lang.js | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 src/js/00-normalize-source-lang.js diff --git a/src/js/00-normalize-source-lang.js b/src/js/00-normalize-source-lang.js deleted file mode 100644 index 146cd2c..0000000 --- a/src/js/00-normalize-source-lang.js +++ /dev/null @@ -1,23 +0,0 @@ -;(function () { - 'use strict' - - // An AsciiDoc source block written as [source,language-bash] makes Asciidoctor emit - // class="language-language-bash" and data-lang="language-bash". highlight.js then finds no - // matching language and leaves the block plain, and the source toolbox shows "language-bash". - // Strip the redundant prefix before the toolbox (06-copy-to-clipboard.js) and the - // highlight.js bundle (loaded async after this script) read the language. - var REDUNDANT_PREFIX_RX = /^(?:language-)+/i - - ;[].slice.call(document.querySelectorAll('pre.highlight > code[data-lang]')).forEach(function (code) { - var lang = code.dataset.lang - if (!REDUNDANT_PREFIX_RX.test(lang)) return - var normalized = lang.replace(REDUNDANT_PREFIX_RX, '') - code.classList.remove('language-' + lang) - if (normalized) { - code.classList.add('language-' + normalized) - code.dataset.lang = normalized - } else { - delete code.dataset.lang - } - }) -})() From 2c112821e939e999cb222ee79ac4554fc8252971 Mon Sep 17 00:00:00 2001 From: Eric Schneider <37347760+eric-schneider@users.noreply.github.com> Date: Fri, 25 Sep 2026 12:49:39 -0500 Subject: [PATCH 07/10] Minify the vendor scripts and keep their license notices The vendor scripts were meant to be minified, all except floatingui.js, but a misplaced parenthesis made the build skip all of them. Minify them as intended, with a per-file condition. - Minifying drops the license notices from the highlight.js bundle, which its BSD license requires us to keep. Record each bundle's /*! ... */ notices before minifying and put back any that are lost. - The zooming and Floating UI bundles carried no license notice at all, though their MIT licenses ask for one. Add one to each. - A first visit now downloads 88 KB of these scripts compressed, instead of 113 KB. The highlight.js bundle drops from 56 KB to 35 KB. --- gulp.d/tasks/build.js | 21 ++++++++++++++++++--- src/js/vendor/floatingui.bundle.js | 3 +++ src/js/vendor/zooming.bundle.js | 1 + 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/gulp.d/tasks/build.js b/gulp.d/tasks/build.js index ffe3f38..475e273 100644 --- a/gulp.d/tasks/build.js +++ b/gulp.d/tasks/build.js @@ -21,6 +21,21 @@ const postcssVar = require('postcss-custom-properties') const { Transform } = require('stream') const map = (transform) => new Transform({ objectMode: true, transform }) const through = () => map((file, enc, next) => next(null, file)) +// Minifying drops some license notices (/*! ... */ comments), which the licenses of the bundled +// libraries require us to keep, so record them first and put back any that are lost. +const LICENSE_NOTICE_RX = /\/\*![\s\S]*?\*\//g +const recordLicenseNotices = () => + map((file, enc, next) => { + file.licenseNotices = [...new Set(file.contents.toString().match(LICENSE_NOTICE_RX))] + next(null, file) + }) +const restoreLicenseNotices = () => + map((file, enc, next) => { + const code = file.contents.toString() + const lost = (file.licenseNotices || []).filter((notice) => !code.includes(notice)) + if (lost.length) file.contents = Buffer.from(lost.join('\n') + '\n' + code) + next(null, file) + }) const uglify = require('gulp-uglify') const vfs = require('vinyl-fs') const hash = require('gulp-hash') @@ -84,9 +99,9 @@ module.exports = (src, dest, preview) => () => { vfs .src('js/vendor/*([^.])?(.bundle).js', { ...opts, read: false }) .pipe(bundle(opts)) - // .pipe(uglify({ output: { comments: /^! / } })) - .pipe(map((file) => file.relative === 'js/vendor/floatingui.js') - ? through() : uglify({ output: { comments: /^! / } })) + .pipe(recordLicenseNotices()) + .pipe(gulpif((file) => file.relative !== 'js/vendor/floatingui.js', uglify({ output: { comments: /^! / } }))) + .pipe(restoreLicenseNotices()) .pipe(gulpif( !preview, hash({ template: '<%= name %>-<%= hash %><%= ext %>' }))) diff --git a/src/js/vendor/floatingui.bundle.js b/src/js/vendor/floatingui.bundle.js index 96b5364..b2acf9b 100644 --- a/src/js/vendor/floatingui.bundle.js +++ b/src/js/vendor/floatingui.bundle.js @@ -1,3 +1,6 @@ +/*! Floating UI | MIT | Copyright (c) 2021-present Floating UI contributors | https://github.com/floating-ui/floating-ui + * @floating-ui/dom 1.6.12, @floating-ui/core 1.6.8, @floating-ui/utils 0.2.8 + */ /** * Dropdown Example * diff --git a/src/js/vendor/zooming.bundle.js b/src/js/vendor/zooming.bundle.js index 92f0693..e8eafac 100644 --- a/src/js/vendor/zooming.bundle.js +++ b/src/js/vendor/zooming.bundle.js @@ -1,3 +1,4 @@ +/*! zooming 2.1.1 | MIT | Copyright (c) 2017-present Desmond Ding | https://github.com/kingdido999/zooming */ ;(function () { 'use strict' From 32822b54f234b74840c2e3d06c900b17ec522307 Mon Sep 17 00:00:00 2001 From: Eric Schneider <37347760+eric-schneider@users.noreply.github.com> Date: Fri, 25 Sep 2026 15:29:23 -0500 Subject: [PATCH 08/10] Add PowerShell highlighting and drop the redundant TOML registration This brings in the change from the powershell-highlighting branch. - Register highlight.js's PowerShell grammar, which also answers to ps and ps1. The Astra CLI's Windows instructions and the C# driver's NuGet commands are PowerShell, now labeled shell and cs; relabeled, they'll be highlighted here and on IBM Docs. - Stop registering the INI grammar a second time as toml. The INI grammar already answers to toml. - Add a PowerShell sample to the UI preview, since no page labels a block powershell yet. --- preview-src/asciidoc/code.adoc | 12 +++++++++++- src/js/vendor/highlight.bundle.js | 5 ++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/preview-src/asciidoc/code.adoc b/preview-src/asciidoc/code.adoc index b565a4a..7cf1785 100644 --- a/preview-src/asciidoc/code.adoc +++ b/preview-src/asciidoc/code.adoc @@ -83,7 +83,7 @@ public class CurrentDateTime { === Syntax highlighting samples -These samples show how each kind of token is colored in the languages the docs use most. +These samples show how each kind of token is colored in the languages the docs use most, and in PowerShell. .CQL: schema and queries [source,cql] @@ -159,6 +159,16 @@ audit_logging_options: ---- +.PowerShell +[source,powershell] +---- +# Install the Astra CLI on Windows +$env:ASTRA_HOME = "C:\Tools\astra" +New-Item -ItemType Directory -Force -Path $env:ASTRA_HOME | Out-Null +Expand-Archive -Path "$env:TEMP\astra.zip" -DestinationPath $env:ASTRA_HOME -Force +if (-not (Get-Command astra -ErrorAction SilentlyContinue)) { Write-Output "Add $env:ASTRA_HOME to PATH" } +---- + === Line wrapping .Line wrapping is turned off by default diff --git a/src/js/vendor/highlight.bundle.js b/src/js/vendor/highlight.bundle.js index 33e31ea..719fa9f 100644 --- a/src/js/vendor/highlight.bundle.js +++ b/src/js/vendor/highlight.bundle.js @@ -10,7 +10,6 @@ var hljs = require('highlight.js/lib/highlight') var hljsCurl = require('highlightjs-curl') var sql = require('highlight.js/lib/languages/sql') - var ini = require('highlight.js/lib/languages/ini') hljs.registerLanguage('asciidoc', require('highlight.js/lib/languages/asciidoc')) hljs.registerLanguage('bash', require('highlight.js/lib/languages/bash')) @@ -26,7 +25,7 @@ hljs.registerLanguage('graphql', require('./languages/graphql.js')) hljs.registerLanguage('groovy', require('highlight.js/lib/languages/groovy')) hljs.registerLanguage('haskell', require('highlight.js/lib/languages/haskell')) - hljs.registerLanguage('ini', ini) + hljs.registerLanguage('ini', require('highlight.js/lib/languages/ini')) hljs.registerLanguage('java', require('highlight.js/lib/languages/java')) hljs.registerLanguage('javascript', require('highlight.js/lib/languages/javascript')) hljs.registerLanguage('json', require('highlight.js/lib/languages/json')) @@ -38,6 +37,7 @@ hljs.registerLanguage('objectivec', require('highlight.js/lib/languages/objectivec')) hljs.registerLanguage('perl', require('highlight.js/lib/languages/perl')) hljs.registerLanguage('php', require('highlight.js/lib/languages/php')) + hljs.registerLanguage('powershell', require('highlight.js/lib/languages/powershell')) hljs.registerLanguage('properties', require('highlight.js/lib/languages/properties')) hljs.registerLanguage('protobuf', require('highlight.js/lib/languages/protobuf')) hljs.registerLanguage('puppet', require('highlight.js/lib/languages/puppet')) @@ -49,7 +49,6 @@ hljs.registerLanguage('sql', sql) hljs.registerLanguage('cql', require('./languages/cql.js')) hljs.registerLanguage('swift', require('highlight.js/lib/languages/swift')) - hljs.registerLanguage('toml', ini) hljs.registerLanguage('typescript', require('highlight.js/lib/languages/typescript')) hljs.registerLanguage('xml', require('highlight.js/lib/languages/xml')) hljs.registerLanguage('yaml', require('highlight.js/lib/languages/yaml')) From 2f018e195b812b4f6351626ad3d33f5e363a3980 Mon Sep 17 00:00:00 2001 From: Eric Schneider <37347760+eric-schneider@users.noreply.github.com> Date: Fri, 25 Sep 2026 15:29:23 -0500 Subject: [PATCH 09/10] Update the vendored CQL grammar Match highlightjs-cql's dist/cql.cjs, which rewords one comment about DataStax Enterprise's PENDING keyword. Highlighting is unchanged. --- src/js/vendor/languages/cql.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/vendor/languages/cql.js b/src/js/vendor/languages/cql.js index d4827e8..cb23ec7 100644 --- a/src/js/vendor/languages/cql.js +++ b/src/js/vendor/languages/cql.js @@ -668,8 +668,8 @@ function cql(hljs) { relevance: 0, contains: [{ begin: NAME_PART + '(?:\\.' + NAME_PART + ')?', endsParent: true, relevance: 0 }] }, KEYWORDS); - // DataStax Enterprise: DESCRIBE PENDING SEARCH INDEX is parsed outside the grammar, so - // PENDING is a keyword only in that phrase. + // DataStax Enterprise documents DESCRIBE PENDING SEARCH INDEX; PENDING is a keyword only in + // that phrase. var PENDING = { className: 'keyword', begin: /\bPENDING(?=\s+SEARCH\s+INDEX\b)/, relevance: 0 }; // KEY is unreserved (a column may be called key), but PRIMARY KEY is always syntax. var PRIMARY_KEY = { className: 'keyword', begin: /\bPRIMARY\s+KEY\b/, relevance: 0 }; From 4dbfd60faeb6919a860a2cb083b0e6a34595cc14 Mon Sep 17 00:00:00 2001 From: Eric Schneider <37347760+eric-schneider@users.noreply.github.com> Date: Fri, 25 Sep 2026 15:44:49 -0500 Subject: [PATCH 10/10] Color setting names sky blue Setting names share their color with JSON and YAML keys, XML attribute names, and GraphQL argument names. The cyan in use now is only 25 apart from string values (CIEDE2000), and the indigo before it only 8.9 from keywords in light mode. Sky is at least 28.8 from keywords and 33.3 from strings, in both themes. - Contrast on the code background is 5.68:1 in light mode (sky 700) and 6.95:1 in dark mode (sky 400). Every highlighted token in the UI preview still passes WCAG AA in both themes. --- src/css/vars/dark.css | 2 +- src/css/vars/light.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/css/vars/dark.css b/src/css/vars/dark.css index c8cee2b..f8091d5 100644 --- a/src/css/vars/dark.css +++ b/src/css/vars/dark.css @@ -106,7 +106,7 @@ --ds-code-keyword: theme(colors.purple.400); --ds-code-type: theme(colors.lime.400); --ds-code-title: theme(colors.pink.400); - --ds-code-attr: theme(colors.cyan.400); + --ds-code-attr: theme(colors.sky.400); --ds-code-string: theme(colors.emerald.400); --ds-code-number: theme(colors.orange.400); --ds-code-variable: theme(colors.amber.400); diff --git a/src/css/vars/light.css b/src/css/vars/light.css index 46e2d19..942e046 100644 --- a/src/css/vars/light.css +++ b/src/css/vars/light.css @@ -106,7 +106,7 @@ --ds-code-keyword: theme(colors.purple.700); --ds-code-type: theme(colors.lime.700); --ds-code-title: theme(colors.pink.700); - --ds-code-attr: theme(colors.cyan.700); + --ds-code-attr: theme(colors.sky.700); --ds-code-string: theme(colors.emerald.700); --ds-code-number: theme(colors.orange.700); --ds-code-variable: theme(colors.amber.700);