Add extensive comment delimiter metadata to most built-in grammars

This commit is contained in:
Andrew Dupont 2024-04-06 13:00:55 -07:00
parent ace4180d9f
commit b9ee65f6a3
32 changed files with 207 additions and 6 deletions

View File

@ -19,6 +19,14 @@ module.exports = {
asyncArrow: "always",
named: "never"
}],
"no-constant-condition": "off",
"no-unused-vars": [
"warn",
{
varsIgnorePattern: '^_',
argsIgnorePattern: '^_'
}
],
"node/no-missing-require": [
"error",
{

View File

@ -1,6 +1,11 @@
'.source.c, .source.cpp, .source.objc, .source.objcpp':
'editor':
'commentStart': '// '
# Technically, line comments aren't universally valid in C, but all modern
# C compilers support them.
'commentDelimiters':
'block': ['/*', '*/']
'line': '//'
'increaseIndentPattern': '(?x)
^ .* \\{ [^}"\']* $
|^ .* \\( [^\\)"\']* $

View File

@ -1,5 +1,7 @@
'.source.clojure':
'editor':
'commentStart': '; '
'commentDelimiters':
'line': ';'
'autocomplete':
'extraWordCharacters': '-'

View File

@ -1,6 +1,8 @@
'.source.coffee, .source.litcoffee, .source.coffee.md':
'editor':
'commentStart': '# '
'commentDelimiters':
'line': '#'
'.source.coffee':
'editor':
'autoIndentOnPaste': false

View File

@ -1,5 +1,8 @@
'.source.cs':
'editor':
'commentStart': '// '
'commentDelimiters':
'line': '//'
'block': ['/*', '*/']
'increaseIndentPattern': '(?x)\n\t\t^ .* \\{ [^}"\']* $\n\t| ^ \\s* \\{ \\} $\n\t'
'decreaseIndentPattern': '(?x)\n\t\t^ (.*\\*/)? \\s* \\} ( [^}{"\']* \\{ | \\s* while \\s* \\( .* )? [;\\s]* (//.*|/\\*.*\\*/\\s*)? $\n\t'

View File

@ -4,6 +4,8 @@
'editor':
'commentStart': '/*'
'commentEnd': '*/'
'commentDelimiters':
'block': ['/*', '*/']
'foldEndPattern': '(?<!\\*)\\*\\*/|^\\s*\\}|\\/*\\s*@end\\s*\\*\\/'
'autocomplete':
'extraWordCharacters': '-'

View File

@ -3,3 +3,5 @@
'softWrap': true
'commentStart': '<!-- '
'commentEnd': ' -->'
'commentDelimiters':
'block': ['<!--', '-->']

View File

@ -1,6 +1,9 @@
'.source.go':
'editor':
'commentStart': '// '
'commentDelimiters':
'block': ['/*', '*/']
'line': '//'
'increaseIndentPattern': '^.*(\\bcase\\b.*:|\\bdefault\\b:|(\\b(func|if|else|switch|select|for|struct)\\b.*)?{[^}]*|\\([^)]*)$'
'decreaseIndentPattern': '^\\s*(\\bcase\\b.*:|\\bdefault\\b:|}[),]?|\\)[,]?)$'
'decreaseNextIndentPattern': '^\\s*[^\\s()}]+(?<m>[^()]*\\((?:\\g<m>[^()]*|[^()]*)\\))*[^()]*\\)[,]?$'

View File

@ -4,6 +4,8 @@
'editor':
'commentStart': '<!-- '
'commentEnd': ' -->'
'commentDelimiters':
'block': ['<!--', '-->']
'foldEndPattern': '(?x)\n\t\t(</(?i:head|body|table|thead|tbody|tfoot|tr|div|select|fieldset|style|script|ul|ol|li|form|dl|section|article|header|footer|nav|aside)>\n\t\t|^(?!.*?<!--).*?--\\s*>\n\t\t|^<!--\\ end\\ tminclude\\ -->$\n\t\t|<\\?(?:php)?.*\\bend(if|for(each)?|while)\\b\n\t\t|\\{\\{?/(if|foreach|capture|literal|foreach|php|section|strip)\n\t\t|^[^{]*\\}\n\t\t|^\\s*\\)[,;]\n\t\t)'
'increaseIndentPattern': '''(?x)
<(?!\\?|(?:area|base|br|col|frame|hr|html|img|input|link|meta|param)\\b|[^>]*/>)

View File

@ -1,6 +1,9 @@
'.source.java':
'editor':
'commentStart': '// '
'commentDelimiters':
'block': ['/*', '*/']
'line': '//'
'foldEndPattern': '^\\s*(\\}|// \\}\\}\\}$)'
'increaseIndentPattern': '^.*\\{(\\}|[^}"\']*)$|^\\s*(public|private|protected):\\s*$'
'decreaseIndentPattern': '^(.*\\*/)?\\s*\\}|^\\s*(public|private|protected):\\s*$'
@ -9,6 +12,8 @@
'foldEndPattern': '\\*\\*/|^\\s*\\}'
'commentStart': '<%-- '
'commentEnd': ' --%>'
'commentDelimiters':
'block': ['<%--', '-->']
'increaseIndentPattern': '^\\s*<(([^!/?]|%)(?!.+?([/%]>|</.+?>))|[%!]--\\s*$)'
'decreaseIndentPattern': '^\\s*(</[^>]+>|-->|--%>)'
'.text.junit-test-report':
@ -17,6 +22,8 @@
'.source.java-properties':
'editor':
'commentStart': '# '
'commentDelimiters':
'line': '#'
'.keyword.other.documentation.javadoc.java':
'editor':
'completions': [

View File

@ -2,6 +2,9 @@
'editor':
'nonWordCharacters': '/\\()"\':,.;<>~!#@%^&*|+=[]{}`?-…'
'commentStart': '// '
'commentDelimiters':
'block': ['/*', '*/']
'line': '//'
'foldEndPattern': '^\\s*\\}|^\\s*\\]|^\\s*\\)'
'increaseIndentPattern': '(?x)
\\{ [^}"\']*(//.*)? $
@ -16,8 +19,12 @@
'editor':
'commentStart': '{/*'
'commentEnd': '*/}'
'commentDelimiters':
'block': ['{/*', '*/}']
'.source.js .meta.jsx.inside-tag.js':
'editor':
'commentStart': '{/*'
'commentEnd': '*/}'
'commentDelimiters':
'block': ['{/*', '*/}']

View File

@ -1,6 +1,9 @@
".source.css.less":
editor:
commentStart: "// "
commentDelimiters:
line: '//'
block: ['/*', '*/']
autocomplete:
extraWordCharacters: '-'
symbols:

View File

@ -2,4 +2,6 @@
'editor':
'increaseIndentPattern': '^[^\\t ]+:'
'commentStart': '# '
'commentDelimiters':
'line': '#'
'tabType': 'hard'

View File

@ -32,6 +32,7 @@
'NSMutableString'
'NSString'
]
# Comment settings are specified in the `language-c` package.
'.source.objcpp, .source.objc':
'editor':
'foldEndPattern': '(?<!\\*)\\*\\*/|^\\s*\\}|^@end\\b'

View File

@ -4,6 +4,8 @@
'increaseIndentPattern': '^.*\\{\\}?\\s*$'
'decreaseIndentPattern': '^\\s*\\}'
'commentStart': '# '
'commentDelimiters':
'line': '#'
'nonWordCharacters': '/\\()"\':,.;<>~!#^&*|+=[]{}`?-'
'.source.perl6':
'editor':
@ -11,4 +13,6 @@
'increaseIndentPattern': '^.*\\{\\}?\\s*$'
'decreaseIndentPattern': '^\\s*\\}'
'commentStart': '# '
'commentDelimiters':
'line': '#'
'nonWordCharacters': '/\\()"\':,.;<>~!#^&*|+=[]{}`?-'

View File

@ -1,6 +1,9 @@
'.source.php':
'editor':
'commentStart': '// '
'commentDelimiters':
'line': '//'
'block': ['/*', '*/']
'completions': [
'APCIterator'
'AppendIterator'

View File

@ -1,6 +1,8 @@
'.source.plist':
'editor':
'commentStart': '// '
'commentDelimiters':
'line': '//'
'foldEndPattern': '^\\s*(\\}|\\))'
'increaseIndentPattern': '^\\s*(([a-zA-Z_-]+|"[^"]+"|\'[^\']+\')\\s+=\\s+)?[{(](?!.*[)}][;,]?\\s*$)'
'decreaseIndentPattern': '^\\s*(\\}|\\))'

View File

@ -4,6 +4,8 @@
'softTabs': true
'tabLength': 4
'commentStart': '# '
'commentDelimiters':
'line': '#'
'foldEndPattern': '^\\s*[}\\])]'
'increaseIndentPattern': '^\\s*(class|def|elif|else|except|finally|for|if|try|with|while|async\\s+(def|for|with))\\b.*:\\s*$'
'decreaseIndentPattern': '^\\s*(elif|else|except|finally)\\b.*:\\s*$'

View File

@ -1,6 +1,8 @@
'.source.ruby':
'editor':
'commentStart': '# '
'commentDelimiters':
'line': '#'
'increaseIndentPattern': '(?x)^\n (\\s*\n (module|class|(private\\s+)?def\n |unless|if|else|elsif\n |case|when\n |begin|rescue|ensure\n |for|while|until\n |(?= .*? \\b(do|begin|case|if|unless)\\b )\n # the look-ahead above is to quickly discard non-candidates\n ( "(\\\\.|[^\\\\"])*+" # eat a double quoted string\n | \'(\\\\.|[^\\\\\'])*+\' # eat a single quoted string\n | [^#"\'] # eat all but comments and strings\n )*\n ( \\s (do|begin|case)\n | [-+=&|*/~%^<>~](?<!\\$.) \\s*+ (if|unless)\n )\n )\\b\n (?! [^;]*+ ; .*? \\bend\\b )\n |( "(\\\\.|[^\\\\"])*+" # eat a double quoted string\n | \'(\\\\.|[^\\\\\'])*+\' # eat a single quoted string\n | [^#"\'] # eat all but comments and strings\n )*\n (\\( (?! [^\\)]*+ \\) )\n | \\{ (?! [^}]*+ \\} )\n | \\[ (?! [^\\]]*+ \\] )\n )\n ).*$'
'decreaseIndentPattern': '^\\s*([}\\]\\)](,?\\s*(#|$)|\\.[a-zA-Z_]\\w*\\b)|(end|rescue|ensure|else|elsif|when)\\b)'
'.text.html.erb':

View File

@ -1,6 +1,9 @@
'.source.rust':
'editor':
'commentStart': '// '
'commentDelimiters':
'line': '//'
'block': ['/*', '*/']
'increaseIndentPattern': '(?x)
^ .* \\{ [^}"\']* $
|^ .* \\( [^\\)"\']* $

View File

@ -24,6 +24,9 @@
'editor':
'nonWordCharacters': '/\\()"\':,.;<>~!#%^&*|+=[]{}`?…'
'commentStart': '// '
'commentDelimiters':
'line': '//'
'block': ['/*', '*/']
'autocomplete':
'symbols':
'mixin':

View File

@ -1,6 +1,8 @@
'.source.shell':
'editor':
'commentStart': '# '
'commentDelimiters':
'line': '#'
'foldEndPattern': '^\\s*(\\}|(done|fi|esac)\\b)'
'increaseIndentPattern': '^\\s*(else|case)\\b|^.*(\\{|\\b(then|do)\\b)$'
'decreaseIndentPattern': '^\\s*(\\}|(elif|else|fi|esac|done)\\b)'

View File

@ -1,6 +1,8 @@
'.source.sql':
'editor':
'commentStart': '-- '
'commentDelimiters':
'line': '--'
'foldEndPattern': '^\\s*\\)'
'increaseIndentPattern': '^\\s*(create|grant|insert|delete|update)\\b|\\((?!.*\\))'
'decreaseIndentPattern': '^\\s*\\)(?!=.*\\()'

View File

@ -1,3 +1,5 @@
'.source.toml':
'editor':
'commentStart': '# '
'commentDelimiters':
'line': '#'

View File

@ -1,6 +1,9 @@
'.source.ts':
'editor':
'commentStart': '// '
'commentDelimiters':
'line': '//'
'block': ['/*', '*/']
'foldEndPattern': '^\\s*\\}|^\\s*\\]|^\\s*\\)'
'increaseIndentPattern': '(?x)
\\{ [^}"\']* $

View File

@ -1,6 +1,9 @@
'.source.tsx':
'editor':
'commentStart': '// '
'commentDelimiters':
'line': '//'
'block': ['/*', '*/']
'foldEndPattern': '^\\s*\\}|^\\s*\\]|^\\s*\\)'
'increaseIndentPattern': '(?x)
\\{ [^}"\']* $
@ -15,6 +18,8 @@
'editor':
'commentStart': '{/* ',
'commentEnd': ' */}',
'commentDelimiters':
'block': ['{/*', '*/}']
'increaseIndentPattern': "{[^}\"']*$|\\[[^\\]\"']*$|\\([^)\"']*$|<[a-zA-Z][^/]*$|^\\s*>$",
'decreaseIndentPattern': "^\\s*(\\s*/[*].*[*]/\\s*)*[}\\])]|^\\s*(</|/>)"

View File

@ -2,6 +2,8 @@
'editor':
'commentStart': '<!-- '
'commentEnd': ' -->'
'commentDelimiters':
'block': ['<!--', '-->']
'foldEndPattern': '^\\s*(</[^>]+>|[/%]>|-->)\\s*$'
'increaseIndentPattern': '^\\s*<(([^!/?]|%)(?!.+?([/%]>|</.+?>))|[%!]--\\s*$)'
'decreaseIndentPattern': '^\\s*(</[^>]+>|-->|--%>)'

View File

@ -2,6 +2,8 @@
'editor':
'autoIndentOnPaste': false
'commentStart': '# '
'commentDelimiters':
'line': '#'
'foldEndPattern': '^\\s*$|^\\s*\\}|^\\s*\\]|^\\s*\\)'
'increaseIndentPattern': '^\\s*.*(:|-) ?(&\\w+)?(\\{[^}"\']*|\\([^)"\']*)?$'
'decreaseIndentPattern': '^\\s+\\}$'

View File

@ -0,0 +1,32 @@
function normalizeDelimiterMetadata(meta = {}) {
let { block } = meta;
if (block && (!Array.isArray(block))) {
let { start, end } = block;
block = [start, end];
}
return { ...meta, block };
}
function interpretDelimiterMetadata(meta) {
let { line, block } = normalizeDelimiterMetadata(meta);
let commentStartString;
let commentEndString;
let commentDelimiters;
let blockIsValid = block != null && Array.isArray(block);
let lineIsValid = typeof line === 'string';
if (lineIsValid || blockIsValid) {
commentDelimiters = block;
if (lineIsValid) {
commentStartString = line;
} else if (blockIsValid) {
[commentStartString, commentEndString] = block;
}
}
return { commentStartString, commentEndString, commentDelimiters };
}
module.exports = {
normalizeDelimiterMetadata,
interpretDelimiterMetadata
};

View File

@ -5925,6 +5925,60 @@ module.exports = class TextEditor {
}
}
// Public: Return information about the appropriate comment delimiters to use
// at a given point in the buffer.
//
// Pulsar allows language bundles to define comment delimiters in several
// places. For instance, a grammar author can place delimiter metadata in the
// grammar definition file, or as scope-specific settings in the ordinary
// config system — or a combination of the two.
//
// In some languages, comment delimiters vary based on position in the
// buffer. (For instance, line comments can't always be used in JavaScript
// JSX blocks, so block comments are much safer.) This method will look for
// any such overrides and return what it thinks are the best delimiters to
// use at a given point.
//
// Some languages don't specify all their delimiters in their configuration,
// but this method will return all the information that it can discern.
//
// * point - A {Point} or point-compatible {Array}.
//
// Returns an {Object} with the following properties:
//
// * `line`: If present, a {String} representing a line comment delimiter.
// (If `undefined`, there is no known line comment delimiter for the given
// buffer position.)
// * `block`: If present, a two-item {Array} containing {String}s
// representing the starting and ending block comment delimiters. (If
// `undefined`, there are no known block comment delimiters for the given
// buffer position.)
//
getCommentDelimitersForPosition(point) {
point = Point.fromObject(point);
const languageMode = this.buffer.getLanguageMode();
let {
commentStartString,
commentEndString,
commentDelimiters
} = languageMode.commentStringsForPosition(point);
if (commentDelimiters) {
return commentDelimiters;
} else {
// Build a delimiters object out of the other data we received. The
// `commentStartString` and `commentEndString` settings aren't meant to
// be comprehensive — they just tell you which delimiter(s) to use to
// comment out a given selection — but they're better than nothing.
if (commentStartString && commentEndString) {
return { block: [commentStartString.trim(), commentEndString.trim()] };
} else if (commentStartString && !commentEndString) {
return { line: commentStartString.trim() };
} else {
return null;
}
}
}
rowRangeForParagraphAtBufferRow(bufferRow) {
if (!NON_WHITESPACE_REGEXP.test(this.lineTextForBufferRow(bufferRow)))
return;

View File

@ -11,6 +11,7 @@ const {
fromFirstMateScopeId
} = require('./first-mate-helpers');
const { selectorMatchesAnyScope } = require('./selectors');
const { normalizeDelimiterMetadata, interpretDelimiterMetadata } = require('./comment-delimiter-utils.js');
const NON_WHITESPACE_REGEX = /\S/;
@ -236,10 +237,19 @@ class TextMateLanguageMode {
const commentEndEntry = commentEndEntries.find(entry => {
return entry.scopeSelector === commentStartEntry.scopeSelector;
});
return {
commentStartString: commentStartEntry && commentStartEntry.value,
commentEndString: commentEndEntry && commentEndEntry.value
};
// If a `commentDelimiters` setting exists, return it in its entirety. This
// can contain more comprehensive delimiter metadata for snippets and other
// purposes.
const commentDelimiters = this.config.get('editor.commentDelimiters', { scope });
if (commentStartEntry) {
return {
commentStartString: commentStartEntry && commentStartEntry.value,
commentEndString: commentEndEntry && commentEndEntry.value,
commentDelimiters: commentDelimiters && normalizeDelimiterMetadata(commentDelimiters)
};
} else if (commentDelimiters) {
return interpretDelimiterMetadata(commentDelimiters);
}
}
/*

View File

@ -8,6 +8,7 @@ const ScopeResolver = require('./scope-resolver');
const Token = require('./token');
const TokenizedLine = require('./tokenized-line');
const { matcherForSelector } = require('./selectors');
const { normalizeDelimiterMetadata, interpretDelimiterMetadata } = require('./comment-delimiter-utils.js');
const createTree = require('./rb-tree');
@ -1105,6 +1106,16 @@ class WASMTreeSitterLanguageMode {
const commentEndEntries = this.config.getAll(
'editor.commentEnd', { scope });
// If a `commentDelimiters` setting exists, return it in its entirety. This
// can contain more comprehensive delimiter metadata for snippets and other
// purposes.
const commentDelimiters = this.config.get('editor.commentDelimiters', {
// This is just general metadata. We don't know the user's intended use
// case. So we should look up the scope descriptor of the original
// position, not the one at the beginning of the line.
scope: this.scopeDescriptorForPosition(position)
});
const commentStartEntry = commentStartEntries[0];
const commentEndEntry = commentEndEntries.find(entry => (
entry.scopeSelector === commentStartEntry.scopeSelector
@ -1113,20 +1124,26 @@ class WASMTreeSitterLanguageMode {
if (commentStartEntry) {
return {
commentStartString: commentStartEntry && commentStartEntry.value,
commentEndString: commentEndEntry && commentEndEntry.value
commentEndString: commentEndEntry && commentEndEntry.value,
commentDelimiters: commentDelimiters && normalizeDelimiterMetadata(commentDelimiters)
};
} else if (commentDelimiters) {
return interpretDelimiterMetadata(commentDelimiters);
}
// Fall back to looking up this information on the grammar.
const { grammar } = this.getSyntaxNodeAndGrammarContainingRange(range);
if (grammar) {
let { commentStrings } = grammar;
let { commentStrings, comments, commentDelimiters } = grammar;
// Some languages don't have block comments, so only check for the start
// delimiter.
if (commentStrings && commentStrings.commentStartString) {
return commentStrings;
}
if (comments || commentDelimiters) {
return interpretDelimiterMetadata(comments ?? commentDelimiters);
}
}
}
@ -4341,6 +4358,7 @@ class LanguageLayer {
);
marker.parentLanguageLayer = this;
// eslint-disable-next-line no-unused-vars
newLanguageLayers++;
}
@ -4362,6 +4380,7 @@ class LanguageLayer {
if (!markersToUpdate.has(marker)) {
this.languageMode.emitRangeUpdate(marker.getRange());
marker.languageLayer.destroy();
// eslint-disable-next-line no-unused-vars
staleLanguageLayers++;
}
}