Retrieve text at cursor position before and after up to boundary character

I am mimicking functionality found in Snowflake’s online worksheet query editor. This revolves around SQL, if that helps any.

I have an editor document with something like this:

select * from users;

select * from billing;

If the user’s cursor is in a position on the first line before the semicolon, I would like to extract select * from users; from the document.

select * from |users;

select * from billing;

If the user’s cursor is after the first semicolon but before the second semicolon, I would like to extract select * from billing; from the document.

select * from users;

select |* from billing;

If the user’s cursor is after the second semicolon, I would extract nothing and would display an error of some kind.

I’m hoping this is a simple-ish task because I am looking at the document text around a cursor position where I find a semicolon before and after. I don’t need to do anything more complex per the language (or so I think).

How do I accomplish this? I see things that appear related in Linter/Autocomplete/findClusterBreak, but it’s not clear how to use those examples/functions.

Any ideas welcome! Thanks.

The sql language mode has a pretty crude parser, but it does separate the document into statements, which sounds like what you’re looking for here. So you could get the syntax tree and figure out which statement node is around or before the cursor from that.

I’ve made progress getting the syntaxTree via the function, but I can’t figure out how to get the SQL statements out the tree as plain text the user sees in the editor.

Try state.sliceDoc(node.from, node.to)

I’m confused by the results I’m getting and how to navigate the different responses.

I’m running these commands:

const cursor = view.state.selection.main.anchor
const tree = syntaxTree(view.state)
const node = tree.resolve(cursor)
const query = view.state.sliceDoc(node.from, node.to)

Here are examples of the results I’m getting with the | marking where the cursor is:

  1. cursor at end of comment line
-- comment|
select * from billing;

select * 
from users;

1. result:

-- comment
select * from billing;

select * 
from users;
  1. cursor “in” comment block text
-- com|ment
select * from billing;

select * 
from users;

2. result:

-- comment
  1. cursor inside select keyword
-- comment
sele|ct * from billing;

select * 
from users;

3. result:

select
  1. cursor next to * character
-- comment
select |* from billing;

select * 
from users;

4. result (desired):

select * from billing;
  1. cursor right before semicolon of statement
-- comment
select * from billing|;

select * 
from users;

5. result (desired):

select * from billing;
  1. cursor between statements in empty line
-- comment
select * from billing;
|
select * 
from users;

6. result:

-- comment
select * from billing;

select * 
from users;
  1. cursor after * character in split line statement
-- comment
select * from billing;

select *|
from users;

7. result (desired):

select * 
from users;
  1. cursor inside users table name
-- comment
select * from billing;

select *
from us|ers;

8. result:

users;
  1. cursor at end of split line statement
-- comment
select * from billing;

select *
from users|;

9. result (desired):

select *
from users;

It seems when the cursor is “inside” a word, the word is returned, but when it’s outside a word boundary, it returns the statement (unless in a comment or empty line). I’m not sure how to handle this. It seems like I’d want to move up to the parent context if inside a word. I can’t tell what the method is to detect what type of Node I’m in.

UPDATE:

I’ve got it after some help from my CTO explaining syntax parsing. Once he explained what the Nodes were doing, all the functions on the Node made sense for how to traverse and poke around the tree. This isn’t final, but was enough to get things going.

const {anchor} = view.state.selection.main
const tree = syntaxTree(view.state)
const curNode = tree.resolve(anchor, -1)

if (curNode.type.name === "Statement") {
    return view.state.sliceDoc(curNode.from, curNode.to)
}
if (curNode.parent?.type.name === "Statement") {
    return view.state.sliceDoc(curNode.parent.from, curNode.parent.to)
}