Added "Show flattened file" command to display a fully flattened top-level file in vscode virtual doc

execute_command now returns content to the client on success, any valid json value
This commit is contained in:
Noah Santschi-Cooney 2021-01-10 23:39:27 +00:00
parent 49324cfb04
commit 26c855f016
No known key found for this signature in database
GPG key ID: 3B22282472C8AE48
8 changed files with 224 additions and 109 deletions

View file

@ -1,7 +1,6 @@
import * as vscode from 'vscode'
import * as lsp from 'vscode-languageclient'
import { Extension } from './extension'
import { tryInstallExecutable } from './glslangValidator'
import { log } from './log'
export type Command = (...args: any[]) => unknown
@ -23,8 +22,31 @@ export function restartExtension(e: Extension): Command {
}
}
export function downloadValidator(e: Extension): Command {
export function virtualMergedDocument(e: Extension): Command {
const getVirtualDocument = async (path: string): Promise<string> => {
const content = await e.lspClient.sendRequest<string>(lsp.ExecuteCommandRequest.type.method, {
command: 'virtualMerge',
arguments: [path]
})
return content
}
const docProvider = new class implements vscode.TextDocumentContentProvider {
onDidChangeEmitter = new vscode.EventEmitter<vscode.Uri>();
onDidChange = this.onDidChangeEmitter.event;
provideTextDocumentContent(uri: vscode.Uri, __: vscode.CancellationToken): vscode.ProviderResult<string> {
return getVirtualDocument(uri.path)
}
}
e.context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider('mcglsl', docProvider))
return async () => {
await tryInstallExecutable(e)
const uri = vscode.window.activeTextEditor.document.uri
const path = vscode.Uri.parse('mcglsl:' + uri.path)
const doc = await vscode.workspace.openTextDocument(path)
await vscode.window.showTextDocument(doc, {preview: true})
}
}

View file

@ -1,7 +1,6 @@
import * as vscode from 'vscode'
import * as lsp from 'vscode-languageclient'
import * as commands from './commands'
import { bootstrapGLSLangValidator } from './glslangValidator'
import { log } from './log'
import { LanguageClient } from './lspClient'
@ -23,10 +22,8 @@ export class Extension {
this.registerCommand('graphDot', commands.generateGraphDot)
this.registerCommand('restart', commands.restartExtension)
this.registerCommand('downlaod', commands.downloadValidator)
this.registerCommand('virtualMerge', commands.virtualMergedDocument)
if(!await bootstrapGLSLangValidator(this)) return
log.info('starting language server...')
this.client = await new LanguageClient(this).startServer()

View file

@ -37,11 +37,12 @@
},
{
"command": "mcshader.restart",
"title": "Reload Language Server",
"title": "Restart Language Server",
"category": "Minecraft Shader"
},{
"command": "mcshader.download",
"title": "Download glslangValidator",
},
{
"command": "mcshader.virtualMerge",
"title": "Show flattened file",
"category": "Minecraft Shader"
}
],

14
server/Cargo.lock generated
View file

@ -1059,9 +1059,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "regex"
version = "1.4.2"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c"
checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
dependencies = [
"aho-corasick",
"memchr",
@ -1071,9 +1071,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.6.21"
version = "0.6.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
[[package]]
name = "remove_dir_all"
@ -1087,7 +1087,7 @@ dependencies = [
[[package]]
name = "rust_lsp"
version = "0.6.0"
source = "git+https://github.com/Strum355/RustLSP?branch=master#629507c387b479d5bdeb0a4eed9ef9aff34801ce"
source = "git+https://github.com/Strum355/RustLSP?branch=master#1f6d533300fd64a739930fd42f0572328be48469"
dependencies = [
"log",
"lsp-types",
@ -1212,9 +1212,9 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
[[package]]
name = "smallvec"
version = "1.6.0"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a55ca5f3b68e41c979bf8c46a6f1da892ca4db8f94023ce0bd32407573b1ac0"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]]
name = "smithay-client-toolkit"

165
server/src/commands.rs Normal file
View file

@ -0,0 +1,165 @@
use std::collections::HashMap;
use std::rc::Rc;
use std::cell::RefCell;
use std::fs::OpenOptions;
use std::io::prelude::*;
use serde_json::Value;
use petgraph::{dot, graph::NodeIndex};
use anyhow::{Result, anyhow};
use std::fs;
use crate::{graph::CachedStableGraph, merge_views};
use crate::dfs;
pub struct CustomCommandProvider {
commands: HashMap<String, Box<dyn Invokeable>>
}
impl CustomCommandProvider {
pub fn new(commands: Vec<(&str, Box<dyn Invokeable>)>) -> CustomCommandProvider {
CustomCommandProvider{
commands: commands.into_iter().map(|tup| {
(tup.0.into(), tup.1)
}).collect(),
}
}
pub fn execute(&self, command: &str, args: Vec<Value>) -> Result<Value, String> {
if self.commands.contains_key(command) {
return self.commands.get(command).unwrap().run_command(args);
}
Err("command doesn't exist".into())
}
}
pub trait Invokeable {
fn run_command(&self, arguments: Vec<Value>) -> Result<Value, String>;
}
pub struct GraphDotCommand {
pub graph: Rc<RefCell<CachedStableGraph>>
}
impl Invokeable for GraphDotCommand {
fn run_command(&self, arguments: Vec<Value>) -> Result<Value, String> {
let rootpath = arguments.get(0).unwrap().to_string();
let rootpath = String::from(rootpath.trim_start_matches('"').trim_end_matches('"'));
let filepath = rootpath + "/graph.dot";
eprintln!("generating dot file at {}", filepath);
let mut file = OpenOptions::new()
.truncate(true)
.write(true)
.create(true)
.open(filepath)
.unwrap();
let mut write_data_closure = || -> Result<(), std::io::Error> {
let graph = self.graph.as_ref();
file.seek(std::io::SeekFrom::Start(0))?;
file.write_all(dot::Dot::new(&(graph.borrow().graph)).to_string().as_bytes())?;
file.flush()?;
file.seek(std::io::SeekFrom::Start(0))?;
Ok(())
};
match write_data_closure() {
Err(err) => Err(format!("Error generating graphviz data: {}", err)),
_ => Ok(Value::Null)
}
}
}
pub struct VirtualMergedDocument {
pub graph: Rc<RefCell<CachedStableGraph>>
}
impl VirtualMergedDocument {
// TODO: DUPLICATE CODE
fn get_file_toplevel_ancestors(&self, uri: &str) -> Result<Option<Vec<petgraph::stable_graph::NodeIndex>>> {
let curr_node = match self.graph.borrow_mut().find_node(uri) {
Some(n) => n,
None => return Err(anyhow!("node not found")),
};
let roots = self.graph.borrow().collect_root_ancestors(curr_node);
if roots.is_empty() {
return Ok(None);
}
Ok(Some(roots))
}
pub fn get_dfs_for_node(&self, root: NodeIndex) -> Result<Vec<(NodeIndex, Option<NodeIndex>)>, dfs::error::CycleError> {
let graph_ref = self.graph.borrow();
let dfs = dfs::Dfs::new(&graph_ref, root);
dfs.collect::<Result<Vec<_>, _>>()
}
pub fn load_sources(&self, nodes: &[(NodeIndex, Option<NodeIndex>)]) -> Result<HashMap<String, String>> {
let mut sources = HashMap::new();
for node in nodes {
let graph = self.graph.borrow();
let path = graph.get_node(node.0);
if sources.contains_key(path) {
continue;
}
let source = fs::read_to_string(path)?;
sources.insert(path.clone(), source);
}
Ok(sources)
}
}
impl Invokeable for VirtualMergedDocument {
fn run_command(&self, arguments: Vec<Value>) -> Result<Value, String> {
let path = arguments.get(0).unwrap().to_string();
let path = String::from(path.trim_start_matches('"').trim_end_matches('"'));
let file_ancestors = match self.get_file_toplevel_ancestors(&path) {
Ok(opt) => match opt {
Some(ancestors) => ancestors,
None => vec![],
},
Err(e) => return Err(e.to_string()),
};
eprintln!("ancestors for {}:\n\t{:?}", path, file_ancestors.iter().map(|e| self.graph.borrow().graph.node_weight(*e).unwrap().clone()).collect::<Vec<String>>());
// the set of all filepath->content. TODO: change to Url?
let mut all_sources: HashMap<String, String> = HashMap::new();
// if we are a top-level file (this has to be one of the set defined by Optifine, right?)
if file_ancestors.is_empty() {
// gather the list of all descendants
let root = self.graph.borrow_mut().find_node(&path).unwrap();
let tree = match self.get_dfs_for_node(root) {
Ok(tree) => tree,
Err(e) => return Err(e.to_string()),
};
let sources = match self.load_sources(&tree) {
Ok(s) => s,
Err(e) => return Err(e.to_string())
};
all_sources.extend(sources);
let graph = self.graph.borrow();
let view = merge_views::generate_merge_list(&tree, &all_sources, &graph);
return Ok(serde_json::value::Value::String(view));
} else {
return Err(format!("{} is not a top-level file aka has ancestors", path))
};
//Ok(Value::Null)
}
}

View file

@ -2,7 +2,7 @@ use petgraph::stable_graph::NodeIndex;
use crate::graph::CachedStableGraph;
use anyhow::{Result, Error};
use anyhow::Result;
struct VisitCount {
node: NodeIndex,

View file

@ -4,6 +4,7 @@ use rust_lsp::lsp_types::{*, notification::*};
use petgraph::stable_graph::NodeIndex;
use serde_json::Value;
use walkdir::WalkDir;
use std::cell::RefCell;
@ -29,7 +30,7 @@ use regex::Regex;
use lazy_static::lazy_static;
mod graph;
mod provider;
mod commands;
mod lsp_ext;
mod dfs;
mod merge_views;
@ -61,12 +62,20 @@ fn main() {
command_provider: None,
};
langserver.command_provider = Some(provider::CustomCommandProvider::new(vec![(
langserver.command_provider = Some(commands::CustomCommandProvider::new(vec![
(
"graphDot",
Box::new(provider::GraphDotCommand {
Box::new(commands::GraphDotCommand {
graph: Rc::clone(&langserver.graph),
}),
)]));
),
(
"virtualMerge",
Box::new(commands::VirtualMergedDocument{
graph: Rc::clone(&langserver.graph)
})
)
]));
LSPEndpoint::run_server_from_input(&mut stdin.lock(), endpoint_output, langserver);
}
@ -76,7 +85,7 @@ struct MinecraftShaderLanguageServer {
graph: Rc<RefCell<graph::CachedStableGraph>>,
wait: WaitGroup,
root: String,
command_provider: Option<provider::CustomCommandProvider>,
command_provider: Option<commands::CustomCommandProvider>,
}
#[derive(Clone, PartialEq, Eq, Hash)]
@ -596,27 +605,18 @@ impl LanguageServerHandling for MinecraftShaderLanguageServer {
})); */
}
fn execute_command(&mut self, mut params: ExecuteCommandParams, completable: LSCompletable<WorkspaceEdit>) {
params
.arguments
.push(serde_json::Value::String(self.root.clone()));
match self
.command_provider
.as_ref()
.unwrap()
.execute(params.command.as_ref(), params.arguments)
fn execute_command(&mut self, mut params: ExecuteCommandParams, completable: LSCompletable<Option<Value>>) {
params.arguments.push(serde_json::Value::String(self.root.clone()));
match self.command_provider.as_ref().unwrap().execute(&params.command, params.arguments)
{
Ok(_) => {
Ok(resp) => {
eprintln!("executed {} successfully", params.command);
self.endpoint.send_notification(ShowMessage::METHOD, ShowMessageParams {
typ: MessageType::Info,
message: format!("Command {} executed successfully.", params.command),
}).expect("failed to send popup/show message notification");
completable.complete(Ok(WorkspaceEdit {
changes: None,
document_changes: None,
change_annotations: Option::None,
}))
completable.complete(Ok(Some(resp)))
},
Err(err) => {
self.endpoint.send_notification(ShowMessage::METHOD, ShowMessageParams {

View file

@ -1,70 +0,0 @@
use std::collections::HashMap;
use std::rc::Rc;
use std::cell::RefCell;
use std::fs::OpenOptions;
use std::io::prelude::*;
use serde_json::Value;
use petgraph::dot;
use crate::graph::CachedStableGraph;
pub struct CustomCommandProvider {
commands: HashMap<String, Box<dyn Invokeable>>
}
impl CustomCommandProvider {
pub fn new(commands: Vec<(&str, Box<dyn Invokeable>)>) -> CustomCommandProvider {
CustomCommandProvider{
commands: commands.into_iter().map(|tup| {
(tup.0.into(), tup.1)
}).collect(),
}
}
pub fn execute(&self, command: &str, args: Vec<Value>) -> Result<(), String> {
if self.commands.contains_key(command) {
return self.commands.get(command).unwrap().run_command(args);
}
Err("command doesn't exist".into())
}
}
pub trait Invokeable {
fn run_command(&self, arguments: Vec<Value>) -> Result<(), String>;
}
pub struct GraphDotCommand {
pub graph: Rc<RefCell<CachedStableGraph>>
}
impl<'a> Invokeable for GraphDotCommand {
fn run_command(&self, params: Vec<Value>) -> Result<(), String> {
let rootpath = params.get(0).unwrap().to_string();
let rootpath = String::from(rootpath.trim_start_matches('"').trim_end_matches('"'));
let filepath = rootpath + "/graph.dot";
eprintln!("generating dot file at {}", filepath);
let mut file = OpenOptions::new()
.truncate(true)
.write(true)
.create(true)
.open(filepath)
.unwrap();
let mut write_data_closure = || -> Result<(), std::io::Error> {
let graph = self.graph.as_ref();
file.seek(std::io::SeekFrom::Start(0))?;
file.write_all(dot::Dot::new(&(graph.borrow().graph)).to_string().as_bytes())?;
file.flush()?;
file.seek(std::io::SeekFrom::Start(0))?;
Ok(())
};
match write_data_closure() {
Err(err) => Err(format!("Error generating graphviz data: {}", err)),
_ => Ok(())
}
}
}