Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 16 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,25 @@ Standalone `pgsql.tmLanguage.json` targeting Shiki, VS Code, GitHub Linguist, an

## Highlighting

Reference theme: **github-dark**
Actual colors depend on your theme. The table below documents the TextMate scope assigned to each token class — themes map scopes to colors.

| Token | Context | Color | Example |
| Token | Context | Scope | Example |
|-------|---------|-------|---------|
| Keywords | everywhere | red | `SELECT`, `FROM`, `WHERE`, `AND`, `JOIN`, `CREATE TABLE`, `BEGIN`, `END` |
| Operators | everywhere | red | `::`, `=`, `<>`, `\|\|`, `@>`, `->>` |
| Built-in functions | before `(` | blue | `now()`, `count(*)`, `coalesce(a, b)` |
| User-defined functions | before `(` | blue | `get_active_users(100)`, `app.my_func()` |
| Numbers | everywhere | blue | `42`, `3.14` |
| Built-in types | after `::` | green | `x::DATE`, `y::INTEGER` |
| Built-in types | inside `CAST` | green | `CAST(y AS NUMERIC)` |
| Built-in types | before literal | green | `INTERVAL '1 day'`, `DATE '2024-01-01'` |
| Built-in types | DDL columns | green | `CREATE TABLE t (id SERIAL, name TEXT)` |
| Built-in types | function signatures | green | `CREATE FUNCTION f(p_id BIGINT)` |
| Built-in types | PL/pgSQL `DECLARE` | green | `DECLARE v_count INTEGER;` |
| Keywords | everywhere | `keyword` | `SELECT`, `FROM`, `WHERE`, `AND`, `JOIN`, `CREATE TABLE`, `BEGIN`, `END` |
| Operators | everywhere | `keyword.operator` | `::`, `=`, `<>`, `\|\|`, `@>`, `->>` |
| Built-in functions | before `(` | `support.function` | `now()`, `count(*)`, `coalesce(a, b)` |
| User-defined functions | before `(` | `entity.name.function` | `get_active_users(100)`, `app.my_func()` |
| Numbers | everywhere | `constant.numeric` | `42`, `3.14` |
| Built-in types | after `::`, inside `CAST`, before literal, DDL/signatures/`DECLARE` | `entity.name.tag` | `x::DATE`, `CAST(y AS NUMERIC)`, `INTERVAL '1 day'`, `id SERIAL` |
| Built-in types | DML (bare word) | unstyled | `SELECT date, name, text FROM t` |
| Constants | everywhere | purple | `NULL`, `TRUE`, `FALSE` |
| `EXTRACT` fields | inside `EXTRACT()` | purple | `EXTRACT(EPOCH FROM now())` |
| Single-quoted strings | everywhere | light blue | `'hello'`, `E'\n'` |
| Double-quoted identifiers | everywhere | light blue | `"my_table"."column"` |
| Comments | everywhere | grey | `-- line`, `/* block */` |
| Constants | everywhere | `constant.language` | `NULL`, `TRUE`, `FALSE` |
| `EXTRACT` fields | inside `EXTRACT()` | `constant.language` | `EXTRACT(EPOCH FROM now())` |
| Single-quoted strings | everywhere | `string.quoted.single` | `'hello'`, `E'\n'` |
| Dollar-quoted literal | `COMMENT ON … IS`, `SELECT`, `INSERT … VALUES`, `CALL` | `string.unquoted` | `$$ plain text $$`, `$body$ text $body$` |
| Dollar-quoted body | `CREATE FUNCTION/PROCEDURE … AS`, `DO` | `meta.dollar-quote` (full SQL/PL inside) | `$$ BEGIN … END; $$` |
| Dollar-quoted nested | inside any dollar-quoted body (e.g. `EXECUTE $$…$$`) | `meta.dollar-quote` (recursive) | `EXECUTE $q$ SELECT 1 $q$;` |
| Double-quoted identifiers | everywhere | `variable.other` | `"my_table"."column"` |
| Comments | everywhere | `comment` | `-- line`, `/* block */` |
| Identifiers | DML | unstyled | `u.name`, `created_at`, `users` |
| Table after `INTO`/`COPY` | before `(columns)` | unstyled | `INSERT INTO users (name)`, `COPY t (col)` |
| Table after `ON`/`REFERENCES` | before `(columns)` | unstyled | `ON orders (user_id)`, `REFERENCES t(id)` |
Expand Down
139 changes: 134 additions & 5 deletions pgsql.tmLanguage.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,15 @@
{
"include": "#create_other"
},
{
"include": "#do_block"
},
{
"include": "#dml_statement"
},
{
"include": "#comment_on"
},
{
"include": "#general_statement"
},
Expand Down Expand Up @@ -142,7 +148,7 @@
"include": "#create_table_columns"
},
{
"include": "#dollar_quotes"
"include": "#dollar_quote_literal"
},
{
"include": "#comments"
Expand Down Expand Up @@ -269,7 +275,7 @@
"name": "meta.statement.pgsql.create",
"patterns": [
{
"include": "#dollar_quotes"
"include": "#dollar_quote_literal"
},
{
"include": "#comments"
Expand Down Expand Up @@ -320,7 +326,7 @@
},
"dml_statement": {
"comment": "DML statements — excludes keyword_ddl to avoid false positives on column names like data, value, mode, level",
"begin": "(?i)^\\s*(select|insert|update|delete|merge|with|truncate|copy|values|explain|vacuum|analyze|do|call)\\b",
"begin": "(?i)^\\s*(select|insert|update|delete|merge|with|truncate|copy|values|explain|vacuum|analyze|call)\\b",
"beginCaptures": {
"1": {
"name": "keyword.other.pgsql"
Expand All @@ -330,7 +336,7 @@
"name": "meta.statement.pgsql",
"patterns": [
{
"include": "#dollar_quotes"
"include": "#dollar_quote_literal"
},
{
"include": "#comments"
Expand Down Expand Up @@ -385,6 +391,48 @@
}
]
},
"comment_on": {
"comment": "COMMENT ON ... IS $$ ... $$ — value is a plain string literal, not code",
"begin": "(?i)^\\s*(comment)\\s+(on)\\b",
"beginCaptures": {
"1": {
"name": "keyword.ddl.pgsql"
},
"2": {
"name": "keyword.ddl.pgsql"
}
},
"end": ";\\s*",
"name": "meta.statement.pgsql.comment-on",
"patterns": [
{
"comment": "IS keyword preceding the comment text",
"match": "(?i)\\b(is)\\b",
"name": "keyword.other.pgsql"
},
{
"include": "#paren_group_typed"
},
{
"include": "#dollar_quote_literal"
},
{
"include": "#comments"
},
{
"include": "#strings"
},
{
"include": "#keywords_all"
},
{
"include": "#schema_qualified"
},
{
"include": "#misc"
}
]
},
"general_statement": {
"begin": "(^\\s*[a-zA-Z]+)",
"beginCaptures": {
Expand All @@ -396,14 +444,17 @@
"name": "meta.statement.pgsql",
"patterns": [
{
"include": "#dollar_quotes"
"include": "#dollar_quote_literal"
},
{
"include": "#comments"
},
{
"include": "#strings"
},
{
"include": "#paren_group_typed"
},
{
"include": "#cast_function"
},
Expand Down Expand Up @@ -455,6 +506,50 @@
"end": "\\n",
"name": "meta.statement.pgsql.psql"
},
"dollar_quote_literal": {
"comment": "Dollar-quoted string literal — plain string, no SQL highlighting inside",
"begin": "(\\$[\\w_0-9]*\\$)",
"beginCaptures": {
"1": {
"name": "string.unquoted.dollar.pgsql"
}
},
"end": "(\\1)",
"endCaptures": {
"1": {
"name": "string.unquoted.dollar.pgsql"
}
},
"name": "string.unquoted.dollar.pgsql"
},
"do_block": {
"comment": "DO anonymous block — dollar-quoted body is executable PL/pgSQL",
"begin": "(?i)^\\s*(do)\\b",
"beginCaptures": {
"1": {
"name": "keyword.other.pgsql"
}
},
"end": ";\\s*",
"name": "meta.statement.pgsql",
"patterns": [
{
"include": "#dollar_quotes"
},
{
"include": "#comments"
},
{
"include": "#strings"
},
{
"include": "#language_constants"
},
{
"include": "#keywords_all"
}
]
},
"dollar_quotes": {
"comment": "Dollar-quoted body — treated as SQL/PL body with syntax highlighting",
"begin": "(\\$[\\w_0-9]*\\$)",
Expand Down Expand Up @@ -934,6 +1029,40 @@
}
]
},
"paren_group_typed": {
"comment": "Parentheses in function/procedure/aggregate signatures — includes storage_types for parameter type lists (used in COMMENT ON, GRANT, ALTER, DROP, etc.)",
"begin": "\\(",
"end": "\\)",
"patterns": [
{
"include": "#paren_group_typed"
},
{
"include": "#comments"
},
{
"include": "#strings"
},
{
"include": "#storage_types"
},
{
"include": "#cast_type"
},
{
"include": "#keywords_all"
},
{
"include": "#operators"
},
{
"include": "#schema_qualified"
},
{
"include": "#misc"
}
]
},
"support_functions": {
"match": "(?xi)\\b(any_value|array_agg|avg|bit_and|bit_or|bit_xor|bool_and|bool_or|count|every|json_agg|jsonb_agg|json_agg_strict|jsonb_agg_strict|json_object_agg|jsonb_object_agg|json_object_agg_strict|jsonb_object_agg_strict|max|min|range_agg|range_intersect_agg|string_agg|sum|xmlagg|corr|covar_pop|covar_samp|regr_avgx|regr_avgy|regr_count|regr_intercept|regr_r2|regr_slope|regr_sxx|regr_sxy|regr_syy|stddev|stddev_pop|stddev_samp|variance|var_pop|var_samp|mode|percentile_cont|percentile_disc|grouping|row_number|rank|dense_rank|percent_rank|cume_dist|ntile|lag|lead|first_value|last_value|nth_value|ascii|btrim|char_length|character_length|chr|concat|concat_ws|format|initcap|casefold|left|length|lower|lpad|ltrim|md5|normalize|octet_length|overlay|parse_ident|position|quote_ident|quote_literal|quote_nullable|regexp_count|regexp_instr|regexp_like|regexp_match|regexp_matches|regexp_replace|regexp_split_to_array|regexp_split_to_table|regexp_substr|repeat|replace|reverse|right|rpad|rtrim|split_part|starts_with|string_to_array|string_to_table|strpos|substr|substring|to_ascii|to_hex|to_oct|to_bin|translate|trim|unicode_assigned|unistr|upper|abs|cbrt|ceil|ceiling|degrees|div|erf|erfc|exp|factorial|floor|gamma|gcd|lcm|lgamma|ln|log|log10|min_scale|mod|pi|power|radians|random|random_normal|round|scale|sign|sqrt|setseed|trim_scale|trunc|width_bucket|acos|acosd|asin|asind|atan|atand|atan2|atan2d|cos|cosd|cot|cotd|sin|sind|tan|tand|sinh|cosh|tanh|asinh|acosh|atanh|age|clock_timestamp|date_add|date_bin|date_part|date_subtract|date_trunc|extract|isfinite|justify_days|justify_hours|justify_interval|make_date|make_interval|make_time|make_timestamp|make_timestamptz|now|statement_timestamp|timeofday|to_timestamp|transaction_timestamp|pg_sleep|pg_sleep_for|pg_sleep_until|timezone|to_json|to_jsonb|array_to_json|row_to_json|json_build_array|jsonb_build_array|json_build_object|jsonb_build_object|json_object|jsonb_object|json_array|json_scalar|json_serialize|json_array_elements|jsonb_array_elements|json_array_elements_text|jsonb_array_elements_text|json_array_length|jsonb_array_length|json_each|jsonb_each|json_each_text|jsonb_each_text|json_extract_path|jsonb_extract_path|json_extract_path_text|jsonb_extract_path_text|json_object_keys|jsonb_object_keys|json_populate_record|jsonb_populate_record|jsonb_populate_record_valid|json_populate_recordset|jsonb_populate_recordset|json_to_record|jsonb_to_record|json_to_recordset|jsonb_to_recordset|jsonb_set|jsonb_set_lax|jsonb_insert|json_strip_nulls|jsonb_strip_nulls|jsonb_path_exists|jsonb_path_match|jsonb_path_query|jsonb_path_query_array|jsonb_path_query_first|jsonb_path_exists_tz|jsonb_path_match_tz|jsonb_path_query_tz|jsonb_path_query_array_tz|jsonb_path_query_first_tz|jsonb_pretty|json_typeof|jsonb_typeof|array_append|array_cat|array_dims|array_fill|array_length|array_lower|array_ndims|array_position|array_positions|array_prepend|array_remove|array_replace|array_reverse|array_sample|array_shuffle|array_sort|array_to_string|array_upper|cardinality|trim_array|unnest|generate_series|generate_subscripts|nextval|currval|setval|lastval|coalesce|nullif|greatest|least|to_char|to_date|to_number|abbrev|broadcast|family|host|hostmask|inet_merge|inet_same_family|masklen|netmask|network|set_masklen|macaddr8_set7bit|array_to_tsvector|get_current_ts_config|numnode|plainto_tsquery|phraseto_tsquery|websearch_to_tsquery|querytree|setweight|strip|to_tsquery|to_tsvector|json_to_tsvector|jsonb_to_tsvector|ts_delete|ts_filter|ts_headline|ts_rank|ts_rank_cd|ts_rewrite|tsquery_phrase|tsvector_to_array|ts_debug|ts_lexize|ts_parse|ts_token_type|ts_stat|gen_random_uuid|uuidv4|uuidv7|uuid_extract_timestamp|uuid_extract_version|enum_first|enum_last|enum_range|lower|upper|isempty|lower_inc|upper_inc|lower_inf|upper_inf|range_merge|multirange|bit_count|bit_length|crc32|crc32c|get_bit|get_byte|set_bit|set_byte|sha224|sha256|sha384|sha512|convert|convert_from|convert_to|encode|decode|xmlattributes|xmlcomment|xmlconcat|xmlelement|xmlforest|xmlpi|xmlroot|xmlexists|xmltext|xml_is_well_formed|xml_is_well_formed_document|xml_is_well_formed_content|xpath|xpath_exists|xmltable|table_to_xml|query_to_xml|num_nonnulls|num_nulls|current_setting|set_config|pg_cancel_backend|pg_terminate_backend|pg_reload_conf|pg_typeof|pg_notify|pg_column_size|pg_column_compression|pg_database_size|pg_relation_size|pg_size_pretty|pg_size_bytes|pg_table_size|pg_tablespace_size|pg_total_relation_size|pg_indexes_size|pg_backend_pid|pg_blocking_pids|pg_advisory_lock|pg_advisory_lock_shared|pg_advisory_unlock|pg_advisory_unlock_all|pg_advisory_unlock_shared|pg_advisory_xact_lock|pg_advisory_xact_lock_shared|pg_try_advisory_lock|pg_try_advisory_lock_shared|pg_try_advisory_xact_lock|pg_try_advisory_xact_lock_shared|pg_export_snapshot|pg_partition_tree|pg_partition_ancestors|pg_partition_root|format_type|pg_get_constraintdef|pg_get_expr|pg_get_functiondef|pg_get_function_arguments|pg_get_function_result|pg_get_indexdef|pg_get_serial_sequence|pg_get_viewdef|pg_get_keywords|pg_input_is_valid|pg_input_error_info|col_description|obj_description|shobj_description|version|pg_is_in_recovery|pg_current_wal_lsn|pg_wal_lsn_diff|pg_relation_filenode|pg_relation_filepath|area|center|diagonal|diameter|height|isclosed|isopen|npoints|pclose|popen|radius|slope|width|bound_box|suppress_redundant_updates_trigger|tsvector_update_trigger|tsvector_update_trigger_column|bernoulli|system|pg_restore_relation_stats|pg_restore_attribute_stats|pg_clear_relation_stats|pg_clear_attribute_stats|pg_get_acl|has_largeobject_privilege|pg_stat_get_backend_io|pg_stat_reset_backend_stats|pg_stat_get_backend_wal|pg_ls_summariesdir|pg_numa_available|pg_get_loaded_modules)\\s*(?=\\()",
"name": "support.function.pgsql"
Expand Down
10 changes: 10 additions & 0 deletions samples/02-ddl.sql
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ CREATE TABLE app.orders (

COMMENT ON TABLE app.orders IS 'Customer orders';
COMMENT ON COLUMN app.orders.metadata IS 'Arbitrary JSON metadata';
COMMENT ON TYPE order_status IS $$
Governs the lifecycle of an order.

pending: payment not yet confirmed.
confirmed: payment received, awaiting fulfilment.
shipped: dispatched to carrier.
delivered: confirmed receipt by customer.
cancelled: voided before delivery.
$$;
COMMENT ON COLUMN app.orders.notes IS $body$Free-form notes attached to the order.$body$;

-- Unlogged table
CREATE UNLOGGED TABLE session_cache (
Expand Down
47 changes: 47 additions & 0 deletions tests/comment-on-dollar-quote.test.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
-- COMMENT ON with single-quoted string
COMMENT ON TABLE app.orders IS 'Customer orders';
-- ^^^^^^^ keyword.ddl
-- ^^ keyword.ddl
-- ^^^^^ keyword.ddl
-- ^^ keyword.other
-- ^^^^^^^^^^^^^^^^ string.quoted.single

-- COMMENT ON with $$ dollar-quoted string literal
COMMENT ON TYPE wf.reuse_policy IS $$
-- ^^^^^^^ keyword.ddl
-- ^^ keyword.ddl
-- ^^^^ keyword.ddl
-- ^^ keyword.other
-- ^^ string.unquoted.dollar
Governs resubmission when prior closed runs exist.
-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ string.unquoted.dollar
$$;

-- COMMENT ON with labelled dollar-quote $body$ ... $body$
COMMENT ON COLUMN app.orders.notes IS $body$
-- ^^^^^^^ keyword.ddl
-- ^^ keyword.ddl
-- ^^^^^^ keyword.ddl
-- ^^ keyword.other
-- ^^^^^^ string.unquoted.dollar
Free-form text annotation.
-- ^^^^^^^^^^^^^^^^^^^^^^^^ string.unquoted.dollar
$body$;
--^^^^^^ string.unquoted.dollar

-- Content inside COMMENT ON $$ should NOT be highlighted as SQL keywords
COMMENT ON TABLE t IS $$
SELECT is just text here, not a keyword.
-- ^^^^ !keyword
$$;

-- COMMENT ON FUNCTION with type list in signature
COMMENT ON FUNCTION wf.register_activity(TEXT, TEXT, INTERVAL, INT) IS 'test';
-- ^^^^ entity.name.tag
-- ^^^^ entity.name.tag
-- ^^^^^^^^ entity.name.tag
-- ^^^ entity.name.tag

-- Types inside IS '...' should NOT be highlighted (consumed as string)
COMMENT ON TABLE t IS 'TEXT and INT are not types here';
-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ string.quoted.single
28 changes: 28 additions & 0 deletions tests/do-block.test.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-- BEGIN / END inside DO body are highlighted
DO $$ BEGIN NULL; END; $$;
-- ^^^^^ keyword
-- ^^^ keyword

-- IF / THEN / ELSE inside DO body
DO $$ BEGIN IF true THEN NULL; ELSE NULL; END IF; END; $$;
-- ^^ keyword
-- ^^^^ keyword
-- ^^^^ keyword

-- RAISE with severity inside DO body
DO $$ BEGIN RAISE NOTICE 'msg'; END; $$;
-- ^^^^^ keyword
-- ^^^^^^ keyword

-- PERFORM inside DO body
DO $$ BEGIN PERFORM pg_sleep(0); END; $$;
-- ^^^^^^^ keyword

-- WHILE loop inside DO body
DO $$ BEGIN WHILE true LOOP NULL; END LOOP; END; $$;
-- ^^^^^ keyword

-- EXECUTE with nested dollar-quote — inner $$ body is also highlighted as SQL
DO $$ BEGIN EXECUTE $q$ SELECT 1 $q$; END; $$;
-- ^^^^^^^ keyword
-- ^^^^^^ keyword
15 changes: 15 additions & 0 deletions tests/dollar-quote-literal.test.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-- SELECT with $$ dollar-quoted string literal — opening delimiter is string.unquoted.dollar
SELECT $$ hello world $$;
-- ^^ string.unquoted.dollar

-- INSERT with $$ dollar-quoted string literal
INSERT INTO t (x) VALUES ($$some text$$);
-- ^^ string.unquoted.dollar

-- Body text inside $$ in SELECT is part of the string
SELECT $$ hello world $$;
-- ^^^^^^^^^^^ string.unquoted.dollar

-- Keyword inside $$ in SELECT does NOT get keyword scope
SELECT $$ SELECT is just text $$;
-- ^^^^^^ !keyword
21 changes: 21 additions & 0 deletions tests/grant-function-signature.test.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-- GRANT with function type signature
GRANT EXECUTE ON FUNCTION wf.register_activity(TEXT, TEXT, INTERVAL, INT) TO app_role;
-- ^^^^ entity.name.tag
-- ^^^^ entity.name.tag
-- ^^^^^^^^ entity.name.tag
-- ^^^ entity.name.tag

-- REVOKE with function type signature
REVOKE EXECUTE ON FUNCTION wf.register_activity(TEXT, INT) FROM app_role;
-- ^^^^ entity.name.tag
-- ^^^ entity.name.tag

-- ALTER FUNCTION with type signature
ALTER FUNCTION wf.register_activity(TEXT, INT) OWNER TO app_role;
-- ^^^^ entity.name.tag
-- ^^^ entity.name.tag

-- DROP FUNCTION with type signature
DROP FUNCTION wf.register_activity(TEXT, INT);
-- ^^^^ entity.name.tag
-- ^^^ entity.name.tag
Loading