mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-09-11 19:17:09 +03:00
I am in hell
This commit is contained in:
parent
e1cce9ac9a
commit
917fdf551b
@ -7,7 +7,7 @@ module.exports = {
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:node/recommended",
|
||||
"plugin:jsdoc/recommended"
|
||||
// "plugin:jsdoc/recommended"
|
||||
],
|
||||
overrides: [],
|
||||
parserOptions: {
|
||||
|
@ -1,5 +1,5 @@
|
||||
name: 'Ruby'
|
||||
scopeName: 'source.ruby'
|
||||
name: 'Ruby (TS)'
|
||||
scopeName: 'source.ruby.ts'
|
||||
type: 'tree-sitter'
|
||||
parser: 'tree-sitter-ruby'
|
||||
|
||||
|
@ -1,5 +1,342 @@
|
||||
; Keywords
|
||||
; NOTES:
|
||||
;
|
||||
; (#set! final "true") means that any later rule that matches this exact range
|
||||
; will be ignored.
|
||||
;
|
||||
; (#set! shy "true") means that this rule will be ignored if any previous rule
|
||||
; has marked this exact range, whether or not it was marked as final.
|
||||
|
||||
|
||||
(superclass
|
||||
(constant) @entity.name.type.class.ruby
|
||||
.
|
||||
)
|
||||
|
||||
(superclass
|
||||
"<" @punctuation.separator.inheritance.ruby
|
||||
(constant) @entity.other.inherited-class.ruby
|
||||
(#set! final "true")
|
||||
)
|
||||
|
||||
; module [Foo]
|
||||
(module
|
||||
name: (constant) @entity.name.type.module.ruby
|
||||
(#set! final "true")
|
||||
)
|
||||
|
||||
(singleton_class
|
||||
"<<" @keyword.operator.assigment.ruby
|
||||
)
|
||||
|
||||
(call
|
||||
method: (identifier) @keyword.other.special-method (#match? @keyword.other.special-method "^(raise|loop)$")
|
||||
)
|
||||
|
||||
; Mark `new` as a special method in all contexts, from `Foo.new` to
|
||||
; `Foo::Bar::Baz.new` and so on.
|
||||
(call
|
||||
receiver: (_)
|
||||
method: (identifier) @function.method.builtin.ruby
|
||||
(#eq? @function.method.builtin.ruby "new")
|
||||
)
|
||||
|
||||
(superclass
|
||||
(scope_resolution
|
||||
scope: (constant) @entity.other.inherited-class.ruby
|
||||
name: (constant) @entity.other.inherited-class.ruby
|
||||
)
|
||||
)
|
||||
|
||||
(scope_resolution
|
||||
scope: (constant) @support.class.ruby
|
||||
(#set! final "true")
|
||||
)
|
||||
|
||||
(scope_resolution
|
||||
"::" @keyword.operator.namespace.ruby
|
||||
(#set! final "true")
|
||||
)
|
||||
|
||||
(scope_resolution
|
||||
name: (constant) @support.class.ruby
|
||||
(#set! final "true")
|
||||
)
|
||||
|
||||
; ((variable) @keyword.other.special-method
|
||||
; (#match? @keyword.other.special-method "^(extend)$"))
|
||||
|
||||
((identifier) @keyword.other.special-method
|
||||
(#match? @keyword.other.special-method "^(private|protected|public)$"))
|
||||
|
||||
|
||||
; Highlight the interpolation inside of a string, plus the strings that delimit
|
||||
; the interpolation.
|
||||
(
|
||||
(interpolation
|
||||
"#{" @punctuation.section.embedded.begin.ruby
|
||||
"}" @punctuation.section.embedded.end.ruby
|
||||
) @meta.embedded
|
||||
(#set! final "true")
|
||||
)
|
||||
|
||||
; Function calls
|
||||
|
||||
; TODO: The TM grammar scopes this as `keyword.control.pseudo-method.ruby`; decide on
|
||||
; the best name for it.
|
||||
(
|
||||
(identifier) @function.method.builtin.ruby
|
||||
(#eq? @function.method.builtin.ruby "require")
|
||||
)
|
||||
|
||||
(unary
|
||||
"defined?" @function.method.builtin.ruby
|
||||
)
|
||||
|
||||
|
||||
|
||||
(class name: [(constant)]
|
||||
@entity.name.type.class.ruby
|
||||
(#set! final "true"))
|
||||
|
||||
; Scope the entire inside of a class body to `meta.class.ruby`; it's
|
||||
; semantically useful even though it probably won't get highlighted.
|
||||
(class) @meta.class.ruby
|
||||
|
||||
(method
|
||||
name: [(identifier) (constant)] @entity.name.function.ruby
|
||||
(#set! final "true")
|
||||
)
|
||||
; (singleton_method name: [(identifier) (constant)] @function.method)
|
||||
|
||||
; Identifiers
|
||||
|
||||
(global_variable) @variable.other.readwrite.global.ruby
|
||||
|
||||
(class_variable) @variable.other.readwrite.class.ruby
|
||||
|
||||
(instance_variable) @variable.other.readwrite.instance.ruby
|
||||
|
||||
(exception_variable (identifier) @variable.parameter.ruby)
|
||||
(call receiver: (identifier) @variable.other.ruby)
|
||||
|
||||
; (call
|
||||
; receiver: (constant) @support.class.ruby
|
||||
; method: (identifier) @function.method.builtin.ruby
|
||||
; (#eq? @function.method.builtin.ruby "new")
|
||||
; )
|
||||
|
||||
(call
|
||||
method: [(identifier) (constant)] @keyword.other.special-method (#match? @keyword.other.special-method "^(extend)$"))
|
||||
|
||||
|
||||
; (call
|
||||
; method: [(identifier) (constant)] @function.method)
|
||||
|
||||
; (call
|
||||
; method: (scope_resolution
|
||||
; scope: [(constant) (scope_resolution)] @support.class.ruby
|
||||
; "::" @keyword.operator.namespace.ruby
|
||||
; name: [(constant)] @support.class.ruby
|
||||
; )
|
||||
; )
|
||||
|
||||
|
||||
(scope_resolution
|
||||
scope: [(constant) (scope_resolution)]
|
||||
"::" @keyword.operator.namespace.ruby
|
||||
name: [(constant)] @support.class.ruby
|
||||
(#set! final "true")
|
||||
)
|
||||
|
||||
|
||||
; (call
|
||||
; receiver: (constant) @constant.ruby (#match? @constant.ruby "^[A-Z\\d_]+$")
|
||||
; )
|
||||
(call receiver: (constant)
|
||||
@support.class.ruby
|
||||
(#set! final "true")
|
||||
)
|
||||
|
||||
((identifier) @constant.builtin.ruby
|
||||
(#match? @constant.builtin.ruby "^__(FILE|LINE|ENCODING)__$"))
|
||||
|
||||
; Anything that hasn't been highlighted yet is probably a bare identifier. Mark
|
||||
; it as `constant` if it's all uppercase…
|
||||
((constant) @constant.ruby
|
||||
(#match? @constant.ruby "^[A-Z\\d_]+$")
|
||||
(#set! final "true"))
|
||||
|
||||
; …otherwise treat it as a variable.
|
||||
((constant) @variable.other.constant.ruby)
|
||||
|
||||
(self) @variable.language.self.ruby
|
||||
(super) @keyword.control.pseudo-method.ruby
|
||||
|
||||
(block_parameter (identifier) @variable.parameter.function.block.ruby)
|
||||
(block_parameters (identifier) @variable.parameter.function.block.ruby)
|
||||
(destructured_parameter (identifier) @variable.parameter.function.ruby)
|
||||
(hash_splat_parameter (identifier) @variable.parameter.function.splat.ruby)
|
||||
(lambda_parameters (identifier) @variable.parameter.function.lambda.ruby)
|
||||
(method_parameters (identifier) @variable.parameter.function.ruby)
|
||||
(splat_parameter (identifier) @variable.parameter.function.splat.ruby)
|
||||
|
||||
; TODO: We might want to combine the name and the colon so they can get
|
||||
; highlighted together as one scope. Pretty sure there's a way to do that.
|
||||
|
||||
; A keyword-style parameter when defining a method.
|
||||
(keyword_parameter
|
||||
":" @constant.other.symbol.parameter.ruby
|
||||
(#set! final "true")
|
||||
)
|
||||
|
||||
; A keyword-style argument when calling a method.
|
||||
(pair
|
||||
key: (hash_key_symbol) @constant.other.symbol.hashkey.ruby
|
||||
":" @constant.other.symbol.hashkey.ruby
|
||||
(#set! final "true")
|
||||
)
|
||||
|
||||
(optional_parameter
|
||||
name: (identifier) @variable.parameter.function.optional.ruby
|
||||
)
|
||||
|
||||
(
|
||||
(identifier) @support.function.kernel.ruby
|
||||
(#match? @support.function.kernel.ruby "^(abort|at_exit|autoload|binding|callcc|caller|caller_locations|chomp|chop|eval|exec|exit|fork|format|gets|global_variables|gsub|lambda|load|local_variables|open|p|print|printf|proc|putc|puts|rand|readline|readlines|select|set_trace_func|sleep|spawn|sprintf|srand|sub|syscall|system|test|trace_var|trap|untrace_var|warn)$")
|
||||
(#set! final "true")
|
||||
)
|
||||
|
||||
;((constant) @constant.ruby
|
||||
; (#match? @constant.ruby "^[A-Z\\d_]+$"))
|
||||
|
||||
; (identifier) @variable
|
||||
|
||||
; Literals
|
||||
|
||||
; TODO: I can't mark these as @string.quoted.double.ruby yet because the "s
|
||||
; match _any_ delimiter, including single quotes and %Qs. This is probably a
|
||||
; bug in tree-sitter-ruby.
|
||||
(
|
||||
(string
|
||||
"\"" @punctuation.definition.string.begin.ruby
|
||||
(string_content)
|
||||
"\"" @punctuation.definition.string.end.ruby
|
||||
) @string.quoted.ruby
|
||||
(#set! final "true")
|
||||
)
|
||||
|
||||
; (will match empty strings)
|
||||
(string) @string.quoted.ruby
|
||||
|
||||
[
|
||||
(bare_string)
|
||||
(subshell)
|
||||
(heredoc_body)
|
||||
(heredoc_beginning)
|
||||
] @string.unquoted.ruby
|
||||
|
||||
[
|
||||
(simple_symbol)
|
||||
(delimited_symbol)
|
||||
(hash_key_symbol)
|
||||
(bare_symbol)
|
||||
] @constant.other.symbol.ruby
|
||||
|
||||
(regex) @string.special.regex
|
||||
(escape_sequence) @escape
|
||||
|
||||
[
|
||||
(integer)
|
||||
(float)
|
||||
] @constant.numeric.ruby
|
||||
|
||||
(nil) @constant.language.nil.ruby
|
||||
|
||||
[
|
||||
(true)
|
||||
(false)
|
||||
] @constant.language.boolean.ruby
|
||||
|
||||
; TODO: tree-sitter-ruby doesn't currently let us distinguish line comments
|
||||
; from block comments (the =begin/=end syntax). Until it does, the latter will
|
||||
; be scoped as `comment.line` and we just have to live with it — or invent a
|
||||
; way to hack around it in the language mode file once we can inspect the text
|
||||
; itself.
|
||||
;
|
||||
; Likewise, we can't grab the leading `#` and scope it as punctuation the way
|
||||
; the TM grammar does.
|
||||
(comment) @comment.line.number-sign.ruby
|
||||
|
||||
; To distinguish them from the bitwise "|" operator.
|
||||
(block_parameters
|
||||
"|" @punctuation.separator.variable.ruby
|
||||
)
|
||||
|
||||
(binary
|
||||
"|" @keyword.operator.other.ruby
|
||||
)
|
||||
|
||||
; Operators
|
||||
|
||||
"(" @punctuation.brace.round.begin.ruby
|
||||
")" @punctuation.brace.round.end.ruby
|
||||
"[" @punctuation.brace.square.begin.ruby
|
||||
"]" @punctuation.brace.square.end.ruby
|
||||
"{" @punctuation.brace.curly.begin.ruby
|
||||
"}" @punctuation.brace.curly.end.ruby
|
||||
|
||||
(conditional
|
||||
["?" ":"] @keyword.operator.conditional.ruby
|
||||
(#set! final "true")
|
||||
)
|
||||
|
||||
|
||||
[
|
||||
"="
|
||||
"||="
|
||||
"+="
|
||||
"-="
|
||||
"<<"
|
||||
] @keyword.operator.assigment.ruby
|
||||
|
||||
[
|
||||
"||"
|
||||
"&&"
|
||||
] @keyword.operator.logical.ruby
|
||||
|
||||
[
|
||||
"&"
|
||||
] @keyword.operator.other.ruby
|
||||
|
||||
[
|
||||
"=="
|
||||
">="
|
||||
"<="
|
||||
">"
|
||||
"<"
|
||||
] @keyword.operator.comparison.ruby
|
||||
|
||||
[
|
||||
"+"
|
||||
"-"
|
||||
"*"
|
||||
"/"
|
||||
"**"
|
||||
] @keyword.operator.arithmetic.ruby
|
||||
|
||||
[
|
||||
"=>"
|
||||
"->"
|
||||
] @keyword.operator.ruby
|
||||
|
||||
[
|
||||
","
|
||||
";"
|
||||
"."
|
||||
":"
|
||||
] @punctuation.separator.ruby
|
||||
|
||||
; Keywords
|
||||
[
|
||||
"alias"
|
||||
"and"
|
||||
@ -28,119 +365,10 @@
|
||||
"when"
|
||||
"while"
|
||||
"yield"
|
||||
] @keyword
|
||||
] @keyword.control.ruby
|
||||
|
||||
((identifier) @keyword
|
||||
(#match? @keyword "^(private|protected|public)$"))
|
||||
|
||||
; Function calls
|
||||
|
||||
((identifier) @function.method.builtin
|
||||
(#eq? @function.method.builtin "require"))
|
||||
|
||||
"defined?" @function.method.builtin
|
||||
|
||||
(call
|
||||
method: [(identifier) (constant)] @function.method)
|
||||
|
||||
; Function definitions
|
||||
|
||||
(alias (identifier) @function.method)
|
||||
(setter (identifier) @function.method)
|
||||
(method name: [(identifier) (constant)] @function.method)
|
||||
(singleton_method name: [(identifier) (constant)] @function.method)
|
||||
|
||||
; Identifiers
|
||||
|
||||
[
|
||||
(class_variable)
|
||||
(instance_variable)
|
||||
] @property
|
||||
|
||||
((identifier) @constant.builtin
|
||||
(#match? @constant.builtin "^__(FILE|LINE|ENCODING)__$"))
|
||||
|
||||
((constant) @constant
|
||||
(#match? @constant "^[A-Z\\d_]+$"))
|
||||
|
||||
(constant) @constructor
|
||||
|
||||
(self) @variable.builtin
|
||||
(super) @variable.builtin
|
||||
|
||||
(block_parameter (identifier) @variable.parameter)
|
||||
(block_parameters (identifier) @variable.parameter)
|
||||
(destructured_parameter (identifier) @variable.parameter)
|
||||
(hash_splat_parameter (identifier) @variable.parameter)
|
||||
(lambda_parameters (identifier) @variable.parameter)
|
||||
(method_parameters (identifier) @variable.parameter)
|
||||
(splat_parameter (identifier) @variable.parameter)
|
||||
|
||||
(keyword_parameter name: (identifier) @variable.parameter)
|
||||
(optional_parameter name: (identifier) @variable.parameter)
|
||||
|
||||
((identifier) @function.method
|
||||
(#is-not? local))
|
||||
(identifier) @variable
|
||||
|
||||
; Literals
|
||||
|
||||
[
|
||||
(string)
|
||||
(bare_string)
|
||||
(subshell)
|
||||
(heredoc_body)
|
||||
(heredoc_beginning)
|
||||
] @string
|
||||
|
||||
[
|
||||
(simple_symbol)
|
||||
(delimited_symbol)
|
||||
(hash_key_symbol)
|
||||
(bare_symbol)
|
||||
] @string.special.symbol
|
||||
|
||||
(regex) @string.special.regex
|
||||
(escape_sequence) @escape
|
||||
|
||||
[
|
||||
(integer)
|
||||
(float)
|
||||
] @number
|
||||
|
||||
[
|
||||
(nil)
|
||||
(true)
|
||||
(false)
|
||||
]@constant.builtin
|
||||
|
||||
(interpolation
|
||||
"#{" @punctuation.special
|
||||
"}" @punctuation.special) @embedded
|
||||
|
||||
(comment) @comment
|
||||
|
||||
; Operators
|
||||
|
||||
[
|
||||
"="
|
||||
"=>"
|
||||
"->"
|
||||
] @operator
|
||||
|
||||
[
|
||||
","
|
||||
";"
|
||||
"."
|
||||
] @punctuation.delimiter
|
||||
|
||||
[
|
||||
"("
|
||||
")"
|
||||
"["
|
||||
"]"
|
||||
"{"
|
||||
"}"
|
||||
"%w("
|
||||
"%i("
|
||||
] @punctuation.bracket
|
||||
; Any identifiers we haven't caught yet can be given a generic scope.
|
||||
((identifier) @function.method.ruby
|
||||
(#is-not? local)
|
||||
(#set! shy "true")
|
||||
)
|
||||
|
21
packages/language-ruby/grammars/ts/indents-mine.scm
Normal file
21
packages/language-ruby/grammars/ts/indents-mine.scm
Normal file
@ -0,0 +1,21 @@
|
||||
|
||||
[
|
||||
"do"
|
||||
"module"
|
||||
"class"
|
||||
] @indent
|
||||
|
||||
; If the user has typed `if foo` but hasn't typed `end` yet, the only way to
|
||||
; recognize that we should indent is via these anonymous nodes… TO
|
||||
|
||||
"if" @indent
|
||||
"unless" @indent
|
||||
|
||||
; …but that also improperly catches postfix conditionals like `exit if foo`. So
|
||||
; we dedent those to balance it out.
|
||||
(if_modifier) @dedent
|
||||
(unless_modifier) @dedent
|
||||
|
||||
[
|
||||
"end"
|
||||
] @dedent
|
@ -1,24 +1,29 @@
|
||||
[
|
||||
(class)
|
||||
"class"
|
||||
(singleton_class)
|
||||
(method)
|
||||
"def"
|
||||
(singleton_method)
|
||||
(module)
|
||||
(if)
|
||||
(block)
|
||||
(do_block)
|
||||
(argument_list)
|
||||
"module"
|
||||
"if"
|
||||
"else"
|
||||
"unless"
|
||||
; (block)
|
||||
; (argument_list)
|
||||
(case)
|
||||
(while)
|
||||
(until)
|
||||
(for)
|
||||
(begin)
|
||||
(unless)
|
||||
"begin"
|
||||
"do"
|
||||
"rescue"
|
||||
; (unless)
|
||||
"("
|
||||
"{"
|
||||
"["
|
||||
] @indent
|
||||
|
||||
|
||||
|
||||
[
|
||||
"end"
|
||||
")"
|
||||
@ -34,7 +39,7 @@
|
||||
(when)
|
||||
(elsif)
|
||||
(else)
|
||||
(rescue)
|
||||
"rescue"
|
||||
(ensure)
|
||||
] @branch
|
||||
|
||||
|
@ -1,11 +1,132 @@
|
||||
const Parser = require('web-tree-sitter');
|
||||
const ScopeDescriptor = require('./scope-descriptor')
|
||||
const fs = require('fs');
|
||||
// const fs = require('fs');
|
||||
const { Point, Range } = require('text-buffer');
|
||||
const { Emitter } = require('event-kit');
|
||||
|
||||
const initPromise = Parser.init()
|
||||
createTree = require("./rb-tree")
|
||||
const createTree = require("./rb-tree")
|
||||
|
||||
class PositionIndex {
|
||||
constructor () {
|
||||
this.map = new Map
|
||||
// TODO: It probably doesn't actually matter what order these are visited
|
||||
// in.
|
||||
this.order = []
|
||||
this.rangeData = new Map
|
||||
}
|
||||
|
||||
_normalizePoint (point) {
|
||||
return `${point.row},${point.column}`
|
||||
}
|
||||
|
||||
_normalizeRange (syntax) {
|
||||
let { startPosition, endPosition } = syntax.node;
|
||||
return `${this._normalizePoint(startPosition)}/${this._normalizePoint(endPosition)}`
|
||||
}
|
||||
|
||||
_keyToObject (key) {
|
||||
let [row, column] = key.split(',');
|
||||
return { row: Number(row), column: Number(column) }
|
||||
}
|
||||
|
||||
setDataForRange (syntax, props) {
|
||||
let key = this._normalizeRange(syntax);
|
||||
return this.rangeData.set(key, props);
|
||||
}
|
||||
|
||||
getDataForRange (syntax) {
|
||||
let key = this._normalizeRange(syntax);
|
||||
return this.rangeData.get(key);
|
||||
}
|
||||
|
||||
store (syntax, id) {
|
||||
let {
|
||||
node,
|
||||
setProperties: props = {}
|
||||
} = syntax;
|
||||
|
||||
let {
|
||||
startPosition: start,
|
||||
endPosition: end
|
||||
} = node;
|
||||
|
||||
let data = this.getDataForRange(syntax);
|
||||
if (data && data.final) {
|
||||
// A previous rule covering this exact range marked itself as "final." We
|
||||
// should not add an additional scope.
|
||||
return;
|
||||
} else if (data && props.shy) {
|
||||
// This node will only apply if we haven't yet marked this range with
|
||||
// anything.
|
||||
return;
|
||||
} else {
|
||||
// TODO: We may want to handle the case where more than one token will
|
||||
// want to set data for a given range. Do we merge objects? Store each
|
||||
// dataset separately?
|
||||
this.setDataForRange(syntax, props);
|
||||
}
|
||||
|
||||
// We should open this scope at `start`.
|
||||
this.set(node, start, id, 'open');
|
||||
|
||||
// We should close this scope at `end`.
|
||||
this.set(node, end, id, 'close');
|
||||
}
|
||||
|
||||
set (node, point, id, which) {
|
||||
let key = this._normalizePoint(point)
|
||||
if (!this.order.includes(key)) {
|
||||
this.order.push(key);
|
||||
}
|
||||
if (!this.map.has(key)) {
|
||||
this.map.set(key, {
|
||||
open: [],
|
||||
close: [],
|
||||
openNodes: [],
|
||||
closeNodes: []
|
||||
})
|
||||
}
|
||||
let bundle = this.map.get(key);
|
||||
let idBundle = bundle[which];
|
||||
let nodeBundle = bundle[`${which}Nodes`];
|
||||
|
||||
if (which === 'open') {
|
||||
// TODO: For now, assume that if two tokens both open at (X, Y), the one
|
||||
// that spans a greater distance in the buffer will be encountered first.
|
||||
// If that's not true, this logic may need to be more complex.
|
||||
|
||||
// If an earlier token has already opened at this point, we want to open
|
||||
// after it.
|
||||
idBundle.push(id)
|
||||
nodeBundle.push(node)
|
||||
} else {
|
||||
// If an earlier token has already closed at this point, we want to close
|
||||
// before it.
|
||||
idBundle.unshift(id)
|
||||
nodeBundle.unshift(node)
|
||||
}
|
||||
}
|
||||
|
||||
get (point) {
|
||||
let key = this._normalizePoint(point)
|
||||
return this.map.get(key)
|
||||
}
|
||||
|
||||
clear () {
|
||||
this.map.clear()
|
||||
this.rangeData.clear()
|
||||
this.order = []
|
||||
}
|
||||
|
||||
*[Symbol.iterator] () {
|
||||
for (let key of this.order) {
|
||||
let point = this._keyToObject(key);
|
||||
yield [point, this.map.get(key)]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const VAR_ID = 257
|
||||
class WASMTreeSitterLanguageMode {
|
||||
@ -29,7 +150,7 @@ class WASMTreeSitterLanguageMode {
|
||||
this.lang = lang
|
||||
this.syntaxQuery = lang.query(grammar.syntaxQuery)
|
||||
if(grammar.localsQuery) {
|
||||
this.localsQuery = lang.query(grammar.localsQuery)
|
||||
// this.localsQuery = lang.query(grammar.localsQuery)
|
||||
}
|
||||
this.grammar = grammar
|
||||
if(grammar.foldsQuery) {
|
||||
@ -41,7 +162,7 @@ class WASMTreeSitterLanguageMode {
|
||||
|
||||
// Force first highlight
|
||||
this.boundaries = createTree(comparePoints)
|
||||
const startRange = new Range([0, 0], [0, 0])
|
||||
// const startRange = new Range([0, 0], [0, 0])
|
||||
const range = buffer.getRange()
|
||||
this.tree = this.parser.parse(buffer.getText())
|
||||
this.emitter.emit('did-change-highlighting', range)
|
||||
@ -53,6 +174,22 @@ class WASMTreeSitterLanguageMode {
|
||||
});
|
||||
}
|
||||
|
||||
// A hack to force an existing buffer to react to an update in the SCM file.
|
||||
_reloadSyntaxQuery () {
|
||||
// let _oldSyntaxQuery = this.syntaxQuery;
|
||||
this.grammar._reloadQueryFiles()
|
||||
let lang = this.parser.getLanguage()
|
||||
this.syntaxQuery = lang.query(this.grammar.syntaxQuery)
|
||||
// let range = this.buffer.getRange()
|
||||
// this._updateSyntax(range.start, range.end)
|
||||
// Force first highlight
|
||||
this.boundaries = createTree(comparePoints)
|
||||
// const startRange = new Range([0, 0], [0, 0])
|
||||
const range = this.buffer.getRange()
|
||||
this.tree = this.parser.parse(this.buffer.getText())
|
||||
this.emitter.emit('did-change-highlighting', range)
|
||||
}
|
||||
|
||||
getGrammar() {
|
||||
return this.grammar
|
||||
}
|
||||
@ -101,59 +238,160 @@ class WASMTreeSitterLanguageMode {
|
||||
}
|
||||
}
|
||||
|
||||
_updateSyntax(from, to) {
|
||||
const syntax = this.syntaxQuery.captures(this.tree.rootNode, from, to)
|
||||
let oldDataIterator = this.boundaries.ge(from)
|
||||
let oldScopes = []
|
||||
_findInvalidationRange(from, to) {
|
||||
let iterate = (from, to) => {
|
||||
let iterator = this.boundaries.ge(from)
|
||||
|
||||
while( oldDataIterator.hasNext && comparePoints(oldDataIterator.key, to) <= 0 ) {
|
||||
this.boundaries = this.boundaries.remove(oldDataIterator.key)
|
||||
oldScopes = oldDataIterator.value.closeScopeIds
|
||||
oldDataIterator.next()
|
||||
let newFrom = from;
|
||||
let newTo = to;
|
||||
while (iterator.hasNext && comparePoints(iterator.key, to) <= 0) {
|
||||
let { key, value } = iterator
|
||||
let { closeNodes, openNodes } = value
|
||||
|
||||
for (let o of openNodes) {
|
||||
if (comparePoints(o.endPosition, newTo) > 0) {
|
||||
newTo = o.endPosition;
|
||||
}
|
||||
}
|
||||
|
||||
for (let c of closeNodes) {
|
||||
if (comparePoints(c.startPosition, newFrom) < 0) {
|
||||
newFrom = c.startPosition;
|
||||
}
|
||||
}
|
||||
|
||||
iterator.next()
|
||||
}
|
||||
|
||||
let didChange = comparePoints(from, newFrom) !== 0 || comparePoints(to, newTo) !== 0;
|
||||
|
||||
return [newFrom, newTo, didChange];
|
||||
};
|
||||
|
||||
let currentFrom = from;
|
||||
let currentTo = to;
|
||||
let stable = false;
|
||||
while (!stable) {
|
||||
let [newFrom, newTo, didChange] = iterate(currentFrom, currentTo);
|
||||
if (!didChange) {
|
||||
stable = true;
|
||||
break;
|
||||
}
|
||||
currentFrom = newFrom;
|
||||
currentTo = newTo;
|
||||
}
|
||||
|
||||
return [currentFrom, currentTo];
|
||||
}
|
||||
|
||||
_updateSyntax(from, to) {
|
||||
console.log('_updateSyntax', from, to);
|
||||
let [realFrom, realTo] = this._findInvalidationRange(from, to)
|
||||
console.log('(widening to:)', realFrom, realTo);
|
||||
|
||||
from = realFrom;
|
||||
to = realTo;
|
||||
|
||||
const syntax = this.syntaxQuery.captures(this.tree.rootNode, from, to)
|
||||
console.log('captures:', syntax);
|
||||
let oldDataIterator = this.boundaries.ge(from)
|
||||
let oldScopes = []
|
||||
// Remove all boundaries data for the given range.
|
||||
while (oldDataIterator.hasNext && comparePoints(oldDataIterator.key, to) <= 0 ) {
|
||||
let { key, value } = oldDataIterator
|
||||
this.boundaries = this.boundaries.remove(key)
|
||||
oldScopes = value.closeScopeIds
|
||||
oldDataIterator.next()
|
||||
// TODO: Doesn't this mean that we'll miss the last item in the iterator
|
||||
// under certain circumstances?
|
||||
}
|
||||
|
||||
// TODO: Still don't quite understand this; need to revisit.
|
||||
oldScopes = oldScopes || []
|
||||
syntax.forEach(({node, name}) => {
|
||||
let id = this.scopeNames.get(name)
|
||||
if(!id) {
|
||||
this.lastId += 2
|
||||
id = this.lastId
|
||||
const newId = this.lastId;
|
||||
this.scopeNames.set(name, newId)
|
||||
this.scopeIds.set(newId, name)
|
||||
}
|
||||
// })
|
||||
|
||||
let old = this.boundaries.get(node.startPosition)
|
||||
if(old) {
|
||||
old.openNode = node
|
||||
if(old.openScopeIds.length === 0) {
|
||||
old.openScopeIds = [id]
|
||||
}
|
||||
} else {
|
||||
this.boundaries = this.boundaries.insert(node.startPosition, {
|
||||
closeScopeIds: [...oldScopes],
|
||||
openScopeIds: [id],
|
||||
openNode: node,
|
||||
position: node.startPosition
|
||||
})
|
||||
oldScopes = [id]
|
||||
}
|
||||
if (!this.positionIndex) {
|
||||
this.positionIndex = new PositionIndex();
|
||||
}
|
||||
this.positionIndex.clear()
|
||||
|
||||
old = this.boundaries.get(node.endPosition)
|
||||
if(old) {
|
||||
old.closeNode = node
|
||||
if(old.closeScopeIds.length === 0) old.closeScopeIds = [id]
|
||||
} else {
|
||||
this.boundaries = this.boundaries.insert(node.endPosition, {
|
||||
closeScopeIds: [id],
|
||||
openScopeIds: [],
|
||||
closeNode: node,
|
||||
position: node.endPosition
|
||||
})
|
||||
}
|
||||
})
|
||||
console.log('oldScopes:', oldScopes.map(s => this.scopeForId(s)));
|
||||
|
||||
let inspect = (id) => {
|
||||
if (Array.isArray(id)) {
|
||||
return id.map(i => inspect(i))
|
||||
}
|
||||
return this.scopeForId(id)
|
||||
};
|
||||
|
||||
syntax.forEach((s) => {
|
||||
let { name } = s
|
||||
let id = this.findOrCreateScopeId(name)
|
||||
|
||||
// PositionIndex takes all our syntax tokens and consolidates them into a
|
||||
// fixed set of boundaries to visit in order. If a token has data, it
|
||||
// sets that data so that a later token for the same range can read it.
|
||||
this.positionIndex.store(s, id)
|
||||
});
|
||||
|
||||
for (let [point, data] of this.positionIndex) {
|
||||
let bundle = {
|
||||
closeScopeIds: [...data.close],
|
||||
openScopeIds: [...data.open],
|
||||
closeNodes: [...data.closeNodes],
|
||||
openNodes: [...data.openNodes],
|
||||
position: point
|
||||
}
|
||||
console.log('inserting', bundle, 'at point:', point, 'close:', inspect(bundle.closeScopeIds), 'open:', inspect(bundle.openScopeIds));
|
||||
this.boundaries = this.boundaries.insert(point, bundle)
|
||||
}
|
||||
|
||||
// syntax.forEach(({ node, name }) => {
|
||||
// // let id = this.scopeNames.get(name)
|
||||
// // console.log(' handling node:', name, node);
|
||||
// // if (!id) {
|
||||
// // this.lastId += 2
|
||||
// // id = this.lastId
|
||||
// // const newId = this.lastId;
|
||||
// // this.scopeNames.set(name, newId)
|
||||
// // this.scopeIds.set(newId, name)
|
||||
// // }
|
||||
// let id = this.findOrCreateScopeId(name)
|
||||
// let old = this.boundaries.get(node.startPosition)
|
||||
// if (old) {
|
||||
// // console.log(' found node:', this.scopeForId(id));
|
||||
// old.openNode = node
|
||||
// if (old.openScopeIds.length === 0) {
|
||||
// old.openScopeIds = [id]
|
||||
// }
|
||||
// } else {
|
||||
// let bundle = {
|
||||
// closeScopeIds: [...oldScopes],
|
||||
// openScopeIds: [id],
|
||||
// openNode: node,
|
||||
// position: node.startPosition
|
||||
// }
|
||||
// console.log('inserting close', s(bundle.closeScopeIds), 'open', s(bundle.openScopeIds), 'at', node.startPosition);
|
||||
// this.boundaries = this.boundaries.insert(node.startPosition, bundle)
|
||||
// oldScopes = [id]
|
||||
// }
|
||||
//
|
||||
// old = this.boundaries.get(node.endPosition)
|
||||
// if (old) {
|
||||
// old.closeNode = node
|
||||
// if (old.closeScopeIds.length === 0) {
|
||||
// old.closeScopeIds = [id]
|
||||
// }
|
||||
// } else {
|
||||
// this.boundaries = this.boundaries.insert(node.endPosition, {
|
||||
// closeScopeIds: [id],
|
||||
// openScopeIds: [],
|
||||
// closeNode: node,
|
||||
// position: node.endPosition
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
|
||||
console.log('closing', oldScopes.map(s => this.scopeForId(s)), 'at the end of the document');
|
||||
this.boundaries = this.boundaries.insert(Point.INFINITY, {
|
||||
closeScopeIds: [...oldScopes],
|
||||
openScopeIds: [],
|
||||
@ -161,6 +399,66 @@ class WASMTreeSitterLanguageMode {
|
||||
})
|
||||
}
|
||||
|
||||
// _updateSyntax(from, to) {
|
||||
// const syntax = this.syntaxQuery.captures(this.tree.rootNode, from, to)
|
||||
// let oldDataIterator = this.boundaries.ge(from)
|
||||
// let oldScopes = []
|
||||
//
|
||||
// while( oldDataIterator.hasNext && comparePoints(oldDataIterator.key, to) <= 0 ) {
|
||||
// this.boundaries = this.boundaries.remove(oldDataIterator.key)
|
||||
// oldScopes = oldDataIterator.value.closeScopeIds
|
||||
// oldDataIterator.next()
|
||||
// }
|
||||
//
|
||||
// oldScopes = oldScopes || []
|
||||
// syntax.forEach(({node, name}) => {
|
||||
// let id = this.scopeNames.get(name)
|
||||
// if(!id) {
|
||||
// this.lastId += 2
|
||||
// id = this.lastId
|
||||
// const newId = this.lastId;
|
||||
// this.scopeNames.set(name, newId)
|
||||
// this.scopeIds.set(newId, name)
|
||||
// }
|
||||
// // })
|
||||
//
|
||||
// let old = this.boundaries.get(node.startPosition)
|
||||
// if(old) {
|
||||
// old.openNode = node
|
||||
// if(old.openScopeIds.length === 0) {
|
||||
// old.openScopeIds = [id]
|
||||
// }
|
||||
// } else {
|
||||
// this.boundaries = this.boundaries.insert(node.startPosition, {
|
||||
// closeScopeIds: [...oldScopes],
|
||||
// openScopeIds: [id],
|
||||
// openNode: node,
|
||||
// position: node.startPosition
|
||||
// })
|
||||
// oldScopes = [id]
|
||||
// }
|
||||
//
|
||||
// old = this.boundaries.get(node.endPosition)
|
||||
// if(old) {
|
||||
// old.closeNode = node
|
||||
// if(old.closeScopeIds.length === 0) old.closeScopeIds = [id]
|
||||
// } else {
|
||||
// this.boundaries = this.boundaries.insert(node.endPosition, {
|
||||
// closeScopeIds: [id],
|
||||
// openScopeIds: [],
|
||||
// closeNode: node,
|
||||
// position: node.endPosition
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// this.boundaries = this.boundaries.insert(Point.INFINITY, {
|
||||
// closeScopeIds: [...oldScopes],
|
||||
// openScopeIds: [],
|
||||
// position: Point.INFINITY
|
||||
// })
|
||||
// }
|
||||
|
||||
_prepareInvalidations() {
|
||||
let nodes = this.oldNodeTexts
|
||||
let parentScopes = createTree(comparePoints)
|
||||
@ -299,33 +597,106 @@ class WASMTreeSitterLanguageMode {
|
||||
|
||||
classNameForScopeId(scopeId) {
|
||||
const scope = this.scopeIds.get(scopeId)
|
||||
if(scope) return `syntax--${scope.replace(/\./g, ' syntax--')}`
|
||||
if (scope) {
|
||||
return `syntax--${scope.replace(/\./g, ' syntax--')}`
|
||||
}
|
||||
}
|
||||
|
||||
scopeForId(scopeId) {
|
||||
return this.scopeIds[scopeId]
|
||||
scopeForId (scopeId) {
|
||||
return this.scopeIds.get(scopeId)
|
||||
}
|
||||
|
||||
scopeDescriptorForPosition(position) {
|
||||
if(!this.tree) return new ScopeDescriptor({scopes: ['text']})
|
||||
const current = Point.fromObject(position)
|
||||
let begin = Point.fromObject(position)
|
||||
findOrCreateScopeId (name) {
|
||||
let id = this.scopeNames.get(name)
|
||||
if (!id) {
|
||||
this.lastId += 2
|
||||
id = this.lastId
|
||||
const newId = this.lastId;
|
||||
this.scopeNames.set(name, newId)
|
||||
this.scopeIds.set(newId, name)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
syntaxTreeScopeDescriptorForPosition(point) {
|
||||
point = this.buffer.clipPosition(Point.fromObject(point));
|
||||
|
||||
// If the position is the end of a line, get node of left character instead of newline
|
||||
// This is to match TextMate behaviour, see https://github.com/atom/atom/issues/18463
|
||||
if (
|
||||
point.column > 0 &&
|
||||
point.column === this.buffer.lineLengthForRow(point.row)
|
||||
) {
|
||||
point = point.copy();
|
||||
point.column--;
|
||||
}
|
||||
|
||||
let scopes = [];
|
||||
|
||||
let root = this.tree.rootNode;
|
||||
let rangeIncludesPoint = (start, end, point) => {
|
||||
return comparePoints(start, point) <= 0 && comparePoints(end, point) >= 0
|
||||
};
|
||||
|
||||
let iterate = (node, isAnonymous = false) => {
|
||||
let { startPosition: start, endPosition: end } = node;
|
||||
if (rangeIncludesPoint(start, end, point)) {
|
||||
scopes.push(isAnonymous ? `"${node.type}"` : node.type);
|
||||
let namedChildrenIds = node.namedChildren.map(c => c.typeId);
|
||||
for (let child of node.children) {
|
||||
let isAnonymous = !namedChildrenIds.includes(child.typeId);
|
||||
iterate(child, isAnonymous);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
iterate(root);
|
||||
|
||||
scopes.unshift(this.grammar.scopeName);
|
||||
return new ScopeDescriptor({ scopes });
|
||||
}
|
||||
|
||||
scopeDescriptorForPosition (point) {
|
||||
// If the position is the end of a line, get scope of left character instead of newline
|
||||
// This is to match TextMate behaviour, see https://github.com/atom/atom/issues/18463
|
||||
if (
|
||||
point.column > 0 &&
|
||||
point.column === this.buffer.lineLengthForRow(point.row)
|
||||
) {
|
||||
point = point.copy();
|
||||
point.column--;
|
||||
}
|
||||
|
||||
if (!this.tree) {
|
||||
return new ScopeDescriptor({scopes: ['text']})
|
||||
}
|
||||
const current = Point.fromObject(point, true)
|
||||
let begin = Point.fromObject(point, true)
|
||||
begin.column = 0
|
||||
const end = Point.fromObject([begin.row+1, 0])
|
||||
|
||||
const end = Point.fromObject([begin.row + 1, 0])
|
||||
this._updateBoundaries(begin, end)
|
||||
const it = this.boundaries.ge(begin)
|
||||
if(!it.value) return new ScopeDescriptor({scopes: ['text']})
|
||||
|
||||
// Start at the beginning.
|
||||
const it = this.boundaries.ge(new Point(0, 0))
|
||||
if (!it.value) {
|
||||
return new ScopeDescriptor({scopes: ['text']})
|
||||
}
|
||||
|
||||
let scopeIds = []
|
||||
while(comparePoints(it.key, current) <= 0) {
|
||||
while (comparePoints(it.key, current) <= 0) {
|
||||
const closing = new Set(it.value.closeScopeIds)
|
||||
scopeIds = scopeIds.filter(s => !closing.has(s))
|
||||
scopeIds.push(...it.value.openScopeIds)
|
||||
if(!it.hasNext) break
|
||||
if (!it.hasNext) { break }
|
||||
it.next()
|
||||
}
|
||||
|
||||
const scopes = scopeIds.map(id => this.classNameForScopeId(id).replace(/^syntax--/, '').replace(/\s?syntax--/g, '.'))
|
||||
const scopes = scopeIds.map(id => this.scopeForId(id))
|
||||
|
||||
if (scopes.length === 0 || scopes[0] !== this.grammar.scopeName) {
|
||||
scopes.unshift(this.grammar.scopeName);
|
||||
}
|
||||
return new ScopeDescriptor({scopes})
|
||||
}
|
||||
|
||||
@ -350,34 +721,83 @@ class WASMTreeSitterLanguageMode {
|
||||
}
|
||||
|
||||
suggestedIndentForBufferRow(row, tabLength, options) {
|
||||
if(row === 0) return 0;
|
||||
console.log('suggestedLineForBufferRow', row, tabLength);
|
||||
if (row === 0) { return 0; }
|
||||
|
||||
const lastLineIndent = this.indentLevelForLine(
|
||||
this.buffer.lineForRow(row - 1), tabLength
|
||||
)
|
||||
let amount = lastLineIndent
|
||||
|
||||
console.log('going from', row - 1, 'to', row, this.tree.rootNode);
|
||||
const indents = this.indentsQuery.captures(
|
||||
this.tree.rootNode,
|
||||
{row: row-1, column: 0},
|
||||
{row: row - 1, column: 0},
|
||||
{row: row, column: 0}
|
||||
)
|
||||
const indent = indents.find(i => i.node.startPosition.row === row-1)
|
||||
const lastLineIndent = this.indentLevelForLine(
|
||||
this.buffer.getLines()[row-1], tabLength
|
||||
)
|
||||
console.log('indents:', indents);
|
||||
|
||||
if(indent?.name === 'indent') {
|
||||
return lastLineIndent + 1
|
||||
} else {
|
||||
const suggestion = this.suggestedIndentForEditedBufferRow(row, tabLength)
|
||||
return suggestion !== undefined ? suggestion : lastLineIndent
|
||||
let delta = 0;
|
||||
for (let { name, node } of indents) {
|
||||
let text = node.text;
|
||||
if (!text || !text.length) { continue; }
|
||||
if (name === 'indent') { delta++ }
|
||||
else if (name === 'indent_end') { delta-- }
|
||||
console.log('delta is now:', delta);
|
||||
}
|
||||
|
||||
if (delta > 1) { delta = 1; }
|
||||
if (delta < 0) { delta = 0; }
|
||||
|
||||
return lastLineIndent + delta;
|
||||
}
|
||||
|
||||
// suggestedIndentForEditedBufferRow(row, tabLength) {
|
||||
// if (row === 0) { return 0; }
|
||||
//
|
||||
// const indents = this.indentsQuery.captures(
|
||||
// this.tree.rootNode,
|
||||
// {row: row, column: 0},
|
||||
// {row: row + 1, column: 0}
|
||||
// )
|
||||
//
|
||||
// console.log('edited indents:', indents);
|
||||
//
|
||||
// let currentLineIndent = this.indentLevelForLine(this.buffer.lineForRow(row), tabLength);
|
||||
// let originalLineIndent = this.suggestedIndentForBufferRow(row, tabLength);
|
||||
// // let startingLineIndent = Math.
|
||||
// console.log('starting at', originalLineIndent);
|
||||
//
|
||||
// let delta = 0;
|
||||
// for (let { name, node } of indents) {
|
||||
// if (!node.text?.length) { continue; }
|
||||
//
|
||||
// if (name === 'branch') {
|
||||
// delta--
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (delta === 0) {
|
||||
// return currentLineIndent;
|
||||
// }
|
||||
//
|
||||
// if (delta < -1) { delta = -1; }
|
||||
// return Math.max(0, originalLineIndent + delta);
|
||||
// }
|
||||
|
||||
suggestedIndentForEditedBufferRow(row, tabLength) {
|
||||
const indents = this.indentsQuery.captures(
|
||||
this.tree.rootNode,
|
||||
{row: row, column: 0},
|
||||
{row: row+1, column: 0}
|
||||
)
|
||||
const indent = indents.find(i => i.node.startPosition.row === row)
|
||||
if(indent?.name === "indent_end") {
|
||||
if(this.buffer.getLines()[row].trim() === indent.node.text) {
|
||||
console.log('indents:', indents);
|
||||
const indent = indents.find(i => {
|
||||
return i.node.startPosition.row === row && i.name === 'branch'
|
||||
});
|
||||
console.log('specific indent:', indent);
|
||||
if(indent?.name === "branch") {
|
||||
if(this.buffer.lineForRow(row).trim() === indent.node.text) {
|
||||
const parent = indent.node.parent
|
||||
if(parent) return this.indentLevelForLine(
|
||||
this.buffer.getLines()[parent.startPosition.row],
|
||||
@ -386,7 +806,6 @@ class WASMTreeSitterLanguageMode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from original tree-sitter. I honestly didn't even read this.
|
||||
indentLevelForLine(line, tabLength) {
|
||||
let indentLength = 0;
|
||||
@ -424,9 +843,12 @@ class WASMTreeSitterLanguageMode {
|
||||
}
|
||||
|
||||
_getFoldsAtRow(row) {
|
||||
if(!this.tree) return []
|
||||
const folds = this.foldsQuery.captures(this.tree.rootNode,
|
||||
{row: row, column: 0}, {row: row+1, column: 0})
|
||||
if (!this.tree) { return [] }
|
||||
const folds = this.foldsQuery.captures(
|
||||
this.tree.rootNode,
|
||||
{ row: row, column: 0 },
|
||||
{ row: row + 1, column: 0 }
|
||||
)
|
||||
return folds.filter(fold => fold.node.startPosition.row === row)
|
||||
}
|
||||
}
|
||||
@ -467,3 +889,10 @@ function comparePoints(a, b) {
|
||||
else
|
||||
return rows
|
||||
}
|
||||
|
||||
function isBetweenPoints (point, a, b) {
|
||||
let comp = comparePoints(a, b);
|
||||
let lesser = comp > 0 ? b : a;
|
||||
let greater = comp > 0 ? a : b;
|
||||
return comparePoints(point, lesser) >= 0 && comparePoints(point, greater) <= 0;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user