Skip to content

Commit d3c0869

Browse files
committed
tree-sitter fun 👀
1 parent 86100aa commit d3c0869

File tree

9 files changed

+197
-4
lines changed

9 files changed

+197
-4
lines changed

client/src/commands.ts

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import path = require('path')
12
import * as vscode from 'vscode'
23
import * as lsp from 'vscode-languageclient'
34
import { Extension } from './extension'
@@ -30,7 +31,7 @@ export function virtualMergedDocument(e: Extension): Command {
3031
command: 'virtualMerge',
3132
arguments: [path]
3233
})
33-
} catch(e) {}
34+
} catch (e) { }
3435

3536
return content
3637
}
@@ -40,17 +41,67 @@ export function virtualMergedDocument(e: Extension): Command {
4041
onDidChange = this.onDidChangeEmitter.event
4142

4243
provideTextDocumentContent(uri: vscode.Uri, __: vscode.CancellationToken): vscode.ProviderResult<string> {
43-
return getVirtualDocument(uri.path)
44+
return getVirtualDocument(uri.path.replace('.flattened' + path.extname(uri.path), path.extname(uri.path)))
4445
}
4546
}
4647

4748
e.context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider('mcglsl', docProvider))
4849

4950
return async () => {
51+
if (vscode.window.activeTextEditor.document.languageId != 'glsl') return
52+
53+
const uri = vscode.window.activeTextEditor.document.uri.path
54+
.substring(0, vscode.window.activeTextEditor.document.uri.path.lastIndexOf('.'))
55+
+ '.flattened.'
56+
+ vscode.window.activeTextEditor.document.uri.path
57+
.slice(vscode.window.activeTextEditor.document.uri.path.lastIndexOf('.') + 1)
58+
const path = vscode.Uri.parse(`mcglsl:${uri}`)
59+
60+
const doc = await vscode.workspace.openTextDocument(path)
61+
docProvider.onDidChangeEmitter.fire(path)
62+
await vscode.window.showTextDocument(doc, {
63+
viewColumn: vscode.ViewColumn.Two,
64+
preview: true
65+
})
66+
}
67+
}
68+
69+
export function parseTree(e: Extension): Command {
70+
const getVirtualDocument = async (path: string): Promise<string | null> => {
71+
let content: string = ''
72+
try {
73+
content = await e.lspClient.sendRequest<string>(lsp.ExecuteCommandRequest.type.method, {
74+
command: 'parseTree',
75+
arguments: [path]
76+
})
77+
} catch (e) { }
78+
79+
return content
80+
}
81+
82+
const docProvider = new class implements vscode.TextDocumentContentProvider {
83+
onDidChangeEmitter = new vscode.EventEmitter<vscode.Uri>()
84+
onDidChange = this.onDidChangeEmitter.event
85+
86+
provideTextDocumentContent(uri: vscode.Uri, __: vscode.CancellationToken): vscode.ProviderResult<string> {
87+
if (uri.path.includes('.flattened.')) return ''
88+
return getVirtualDocument(uri.path.substring(0, uri.path.lastIndexOf('.')))
89+
}
90+
}
91+
92+
e.context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider('mcglsl', docProvider))
93+
94+
return async () => {
95+
if (vscode.window.activeTextEditor.document.languageId != 'glsl') return
96+
5097
const uri = vscode.window.activeTextEditor.document.uri
51-
const path = vscode.Uri.parse('mcglsl:' + uri.path)
98+
const path = vscode.Uri.parse(`mcglsl:${uri.path}.ast`)
99+
52100
const doc = await vscode.workspace.openTextDocument(path)
53101
docProvider.onDidChangeEmitter.fire(path)
54-
await vscode.window.showTextDocument(doc, {preview: true})
102+
await vscode.window.showTextDocument(doc, {
103+
viewColumn: vscode.ViewColumn.Two,
104+
preview: true
105+
})
55106
}
56107
}

client/src/extension.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export class Extension {
4848
this.registerCommand('graphDot', commands.generateGraphDot)
4949
this.registerCommand('restart', commands.restartExtension)
5050
this.registerCommand('virtualMerge', commands.virtualMergedDocument)
51+
this.registerCommand('parseTree', commands.parseTree)
5152

5253
log.info('starting language server...')
5354

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@
4141
"command": "mcglsl.virtualMerge",
4242
"title": "Show flattened file",
4343
"category": "Minecraft Shader"
44+
},
45+
{
46+
"command": "mcglsl.parseTree",
47+
"title": "Show parse tree for file",
48+
"category": "Minecraft Shader"
4449
}
4550
],
4651
"languages": [

server/Cargo.lock

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ slog-atomic = "3.1"
3030
once_cell = "1.7"
3131
rand = "0.8"
3232
arc-swap = "1.5.0"
33+
tree-sitter = "0.20.6"
34+
tree-sitter-glsl = "0.1.2"
3335

3436
[dev-dependencies]
3537
tempdir = "0.3"

server/src/commands/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ use std::{collections::HashMap, path::Path};
33
use serde_json::Value;
44

55
use anyhow::{format_err, Result};
6+
use slog_scope::info;
67

78
pub mod graph_dot;
89
pub mod merged_includes;
10+
pub mod parse_tree;
911

1012
pub struct CustomCommandProvider {
1113
commands: HashMap<String, Box<dyn Invokeable>>,
@@ -20,6 +22,9 @@ impl CustomCommandProvider {
2022

2123
pub fn execute(&self, command: &str, args: &[Value], root_path: &Path) -> Result<Value> {
2224
if self.commands.contains_key(command) {
25+
info!("running command";
26+
"command" => command,
27+
"args" => format!("[{}]", args.iter().map(|v| serde_json::to_string(v).unwrap()).collect::<Vec<String>>().join(", ")));
2328
return self.commands.get(command).unwrap().run_command(root_path, args);
2429
}
2530
Err(format_err!("command doesn't exist"))

server/src/commands/parse_tree.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
use std::{
2+
cell::RefCell,
3+
fs,
4+
path::{Path, PathBuf},
5+
rc::Rc,
6+
};
7+
8+
use anyhow::{format_err, Result};
9+
use serde_json::Value;
10+
use slog_scope::warn;
11+
use tree_sitter::{Parser, TreeCursor};
12+
13+
use crate::url_norm::FromJson;
14+
15+
use super::Invokeable;
16+
17+
pub struct TreeSitterSExpr {
18+
pub tree_sitter: Rc<RefCell<Parser>>,
19+
}
20+
21+
impl Invokeable for TreeSitterSExpr {
22+
fn run_command(&self, _: &Path, arguments: &[Value]) -> Result<Value> {
23+
let path = PathBuf::from_json(arguments.get(0).unwrap())?;
24+
25+
warn!("parsing"; "path" => path.to_str().unwrap().to_string());
26+
27+
let source = fs::read_to_string(path)?;
28+
29+
let tree = match self.tree_sitter.borrow_mut().parse(source, None) {
30+
Some(tree) => tree,
31+
None => return Err(format_err!("tree-sitter parsing resulted in no parse tree")),
32+
};
33+
34+
let mut cursor = tree.walk();
35+
36+
let rendered = render_parse_tree(&mut cursor);
37+
38+
Ok(serde_json::value::Value::String(rendered))
39+
}
40+
}
41+
42+
fn render_parse_tree(cursor: &mut TreeCursor) -> String {
43+
let mut string = String::new();
44+
45+
let mut indent = 0;
46+
let mut visited_children = false;
47+
48+
loop {
49+
let node = cursor.node();
50+
51+
let display_name = if node.is_missing() {
52+
format!("MISSING {}", node.kind())
53+
} else if node.is_named() {
54+
node.kind().to_string()
55+
} else {
56+
"".to_string()
57+
};
58+
59+
if visited_children {
60+
if cursor.goto_next_sibling() {
61+
visited_children = false;
62+
} else if cursor.goto_parent() {
63+
visited_children = true;
64+
indent -= 1;
65+
} else {
66+
break;
67+
}
68+
} else {
69+
if !display_name.is_empty() {
70+
let start = node.start_position();
71+
let end = node.end_position();
72+
73+
let field_name = match cursor.field_name() {
74+
Some(name) => name.to_string() + ": ",
75+
None => "".to_string(),
76+
};
77+
78+
string += (" ".repeat(indent)
79+
+ format!("{}{} [{}, {}] - [{}, {}]\n", field_name, display_name, start.row, start.column, end.row, end.column)
80+
.trim_start())
81+
.as_str();
82+
}
83+
84+
if cursor.goto_first_child() {
85+
visited_children = false;
86+
indent += 1;
87+
} else {
88+
visited_children = true;
89+
}
90+
}
91+
}
92+
93+
string
94+
}

server/src/main.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use petgraph::stable_graph::NodeIndex;
77
use serde::Deserialize;
88
use serde_json::{from_value, Value};
99

10+
use tree_sitter::Parser;
1011
use url_norm::FromUrl;
1112

1213
use walkdir::WalkDir;
@@ -65,12 +66,16 @@ fn main() {
6566

6667
let cache_graph = graph::CachedStableGraph::new();
6768

69+
let mut parser = Parser::new();
70+
parser.set_language(tree_sitter_glsl::language()).unwrap();
71+
6872
let mut langserver = MinecraftShaderLanguageServer {
6973
endpoint: endpoint_output.clone(),
7074
graph: Rc::new(RefCell::new(cache_graph)),
7175
root: "".into(),
7276
command_provider: None,
7377
opengl_context: Rc::new(opengl::OpenGlContext::new()),
78+
tree_sitter: Rc::new(RefCell::new(parser)),
7479
log_guard: Some(guard),
7580
};
7681

@@ -87,6 +92,12 @@ fn main() {
8792
graph: langserver.graph.clone(),
8893
}),
8994
),
95+
(
96+
"parseTree",
97+
Box::new(commands::parse_tree::TreeSitterSExpr {
98+
tree_sitter: langserver.tree_sitter.clone(),
99+
}),
100+
),
90101
]));
91102

92103
LSPEndpoint::run_server_from_input(&mut stdin().lock(), endpoint_output, langserver);
@@ -98,6 +109,7 @@ struct MinecraftShaderLanguageServer {
98109
root: PathBuf,
99110
command_provider: Option<commands::CustomCommandProvider>,
100111
opengl_context: Rc<dyn opengl::ShaderValidator>,
112+
tree_sitter: Rc<RefCell<Parser>>,
101113
log_guard: Option<slog_scope::GlobalLoggerGuard>,
102114
}
103115

server/src/test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ fn new_temp_server(opengl_context: Option<Box<dyn opengl::ShaderValidator>>) ->
5050
command_provider: None,
5151
opengl_context: context.into(),
5252
log_guard: Some(guard),
53+
tree_sitter: Rc::new(RefCell::new(Parser::new())),
5354
}
5455
}
5556

0 commit comments

Comments
 (0)