diff --git a/client/src/extension.ts b/client/src/extension.ts index 3174e64..2f77c8c 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -1,40 +1,72 @@ import * as path from 'path' import * as vscode from 'vscode' import * as vscodeLang from 'vscode-languageclient' +import { promptDownload, testExecutable } from './glslangValidator' + +export const glslConfigParam = 'mcglsl.glslangValidatorPath' + +export let outputChannel: vscode.OutputChannel + +let statusBarItem: vscode.StatusBarItem + +let globalContext: vscode.ExtensionContext export async function activate(context: vscode.ExtensionContext) { - const outputChannel = vscode.window.createOutputChannel('vscode-mc-shader') + outputChannel = vscode.window.createOutputChannel('vscode-mc-shader') + globalContext = context - const clientOpts: vscodeLang.LanguageClientOptions = { - documentSelector: [{scheme: 'file', language: 'glsl'}], - outputChannel: outputChannel, - outputChannelName: 'vscode-mc-shader', - synchronize: { - configurationSection: 'mcglsl', - fileEvents: vscode.workspace.createFileSystemWatcher('**/*.{fsh,gsh,vsh,glsl}') - }, + { + if (!testExecutable(vscode.workspace.getConfiguration().get(glslConfigParam))) { + await promptDownload() + } else { + outputChannel.appendLine('glslangValidator found!') + } } - - const serverOpts: vscodeLang.ServerOptions = { - command: context.asAbsolutePath(path.join('server', 'target', 'debug', 'vscode-mc-shader')), - - } - - outputChannel.appendLine('starting language server...') - - const langServer = new vscodeLang.LanguageClient('vscode-mc-shader', serverOpts, clientOpts) - - context.subscriptions.push(langServer.start()) - await langServer.onReady() + { - langServer.onNotification('sampleText', (...nums: number[]) => { - outputChannel.appendLine(`got notif: ${nums.join(' ')}`) - }) + const clientOpts: vscodeLang.LanguageClientOptions = { + documentSelector: [{scheme: 'file', language: 'glsl'}], + outputChannel: outputChannel, + outputChannelName: 'vscode-mc-shader', + synchronize: { + configurationSection: 'mcglsl', + fileEvents: vscode.workspace.createFileSystemWatcher('**/*.{fsh,gsh,vsh,glsl}') + }, + } - langServer.onNotification('update-config', (dir: string) => { - vscode.workspace.getConfiguration().update('mcglsl.glslangValidatorPath', dir, vscode.ConfigurationTarget.Global) - }) + const serverOpts: vscodeLang.ServerOptions = { + command: context.asAbsolutePath(path.join('server', 'target', 'debug', 'vscode-mc-shader')), + } - outputChannel.appendLine('language server started!') + outputChannel.appendLine('starting language server...') + + const langServer = new vscodeLang.LanguageClient('vscode-mc-shader', serverOpts, clientOpts) + + context.subscriptions.push(langServer.start()) + + await langServer.onReady() + + langServer.onNotification('updateConfig', (dir: string) => { + vscode.workspace.getConfiguration().update(glslConfigParam, dir, vscode.ConfigurationTarget.Global) + }) + + langServer.onNotification('status', updateStatus) + + langServer.onNotification('clearStatus', clearStatus) + + outputChannel.appendLine('language server started!') + } +} + +export function updateStatus(icon: string, text: string) { + if(statusBarItem != null) statusBarItem.dispose() + statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left) + statusBarItem.text = icon + " [Minecraft Shaders] " + text + statusBarItem.show() + globalContext.subscriptions.push(statusBarItem) +} + +export function clearStatus() { + if(statusBarItem != null) statusBarItem.dispose() } \ No newline at end of file diff --git a/client/src/glslangValidator.ts b/client/src/glslangValidator.ts new file mode 100644 index 0000000..8d898c4 --- /dev/null +++ b/client/src/glslangValidator.ts @@ -0,0 +1,89 @@ +import * as unzip from 'adm-zip' +import { execSync } from 'child_process' +import { writeFileSync } from 'fs' +import fetch from 'node-fetch' +import { platform } from 'os' +import * as vscode from 'vscode' +import { clearStatus, glslConfigParam, outputChannel, updateStatus } from './extension' + +const url = { + 'win32': 'https://github.com/KhronosGroup/glslang/releases/download/master-tot/glslang-master-windows-x64-Release.zip', + 'linux': 'https://github.com/KhronosGroup/glslang/releases/download/master-tot/glslang-master-linux-Release.zip', + 'darwin': 'https://github.com/KhronosGroup/glslang/releases/download/master-tot/glslang-master-osx-Release.zip' +} + +const config = vscode.workspace.getConfiguration() + +export async function promptDownload() { + const chosen = await vscode.window.showErrorMessage( + `[mc-glsl] glslangValidator not found at: '${config.get(glslConfigParam)}'.`, + {title: 'Download'}, + {title: 'Cancel'} + ) + + if (!chosen || chosen.title !== 'Download') return + + await installExecutable() +} + +async function installExecutable() { + try { + updateStatus('$(cloud-download)', 'Downloading glslangValidator') + + const glslangBin = '/glslangValidator' + (platform() === 'win32' ? '.exe' : '') + const glslangPath = config.get('mcglsl.shaderpacksPath') + glslangBin + + const response = await fetch(url[platform()]) + outputChannel.appendLine('glslangValidator download response status: ' + response.status ) + + const zip = new unzip(await response.buffer()) + + const bin = zip.readFile('bin' + glslangBin) + outputChannel.appendLine('buffer length ' + bin.length) + writeFileSync(glslangPath, bin, {encoding: null, mode: 0o755}) + + // Make sure download was successful + if (!testExecutable(glslangPath)) { + vscode.window.showErrorMessage(`Unexpected error occurred checking for binary at ${glslangPath}. Please try again`) + clearStatus() + throw new Error('failed to install glslangValidator') + } + + // All done! + outputChannel.appendLine(`successfully downloaded glslangValidator to ${glslangPath}`) + vscode.window.showInformationMessage( + `glslangValidator has been downloaded to ${glslangPath}. Your config should be updated automatically.` + ) + config.update('mcglsl.glslangValidatorPath', glslangPath, vscode.ConfigurationTarget.Global) + clearStatus() + } catch (e) { + outputChannel.appendLine(`failed downloading glslangValidator ${e}`) + vscode.window.showErrorMessage(`Failed to install glslangValidator: ${e}`) + clearStatus() + throw e + } + +} + +export function testExecutable(glslangPath?: string): boolean { + glslangPath = glslangPath || config.get(glslConfigParam) + let stdout = '' + try { + stdout = execSync(glslangPath, { + stdio: 'pipe', + }).toString() + } catch (e) { + stdout = (e.stdout.toString() as string) + } + + outputChannel.appendLine('glslangValidator first line stdout: "' + stdout.split('\n')[0] + '"') + const success = stdout.startsWith('Usage') + + if (success) { + outputChannel.appendLine(`glslangValidator found at ${glslangPath}`) + } else { + outputChannel.appendLine(`glslangValidator not found at ${glslangPath}`) + } + + return success +} \ No newline at end of file diff --git a/server/src/main.rs b/server/src/main.rs index eb3c65c..5e05505 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -67,37 +67,39 @@ impl MinecraftShaderLanguageServer { } pub fn gen_initial_graph(&mut self, root: String) { + self.endpoint.send_notification("status", vec!["$(loading~spin)", "Building project..."]).unwrap(); let mut files = HashMap::new(); eprintln!("root of project is {}", root); - for entry_res in walkdir::WalkDir::new(root.clone()).into_iter() { - let entry = match entry_res { - Ok(entry) => entry, - Err(e) => { - eprintln!("error {} {:?}", e.path().unwrap_or(Path::new("")).display(), e); - break; - }, - }; + // filter directories and files not ending in any of the 3 extensions + let file_iter = walkdir::WalkDir::new(root.clone()).into_iter().filter_map(|entry| { + if !entry.is_ok() { + return None; + } + + let entry = entry.unwrap(); let path = entry.path(); - if path.is_dir() { - continue; + return None; } - if !path.is_dir() { - let ext = path.extension().unwrap().to_str().unwrap(); - if ext != "vsh" && ext != "fsh" && ext != "glsl" { - continue; - } + let ext = path.extension().unwrap().to_str().unwrap(); + if ext != "vsh" && ext != "fsh" && ext != "glsl" { + return None; } + Some(String::from(path.to_str().unwrap())) + }); - let includes = self.find_includes(root.as_str(), path.to_str().unwrap()); + // iterate all valid found files, search for includes, add a node into the graph for each + // file and add a file->includes KV into the map + for entry_res in file_iter { + let includes = self.find_includes(root.as_str(), entry_res.as_str()); - let stripped_path = String::from(String::from(path.to_str().unwrap()).trim_start_matches(root.as_str())); + let stripped_path = String::from(String::from(entry_res)); let idx = self.graph.add_node(stripped_path.clone()); - eprintln!("adding {} with\n{:?}", stripped_path.clone(), includes); + //eprintln!("adding {} with\n{:?}", stripped_path.clone(), includes); files.insert(stripped_path, GLSLFile{ idx: idx, includes: includes, @@ -105,20 +107,30 @@ impl MinecraftShaderLanguageServer { } // Add edges between nodes, finding target nodes on weight (value) - for (k, v) in files.into_iter() { + for (_, v) in files.into_iter() { for file in v.includes { let mut iter = self.graph.node_indices(); - eprintln!("searching for {}", file); - let idx = iter.find(|i| self.graph[*i] == file).unwrap(); + //eprintln!("searching for {}", file); + let idx = iter.find(|i| self.graph[*i] == file); + if idx.is_none() { + eprintln!("couldn't find {} in graph", file); + continue; + } //eprintln!("added edge between\n\t{}\n\t{}", k, file); - self.graph.add_edge(v.idx, idx, String::from("includes")); + self.graph.add_edge(v.idx, idx.unwrap(), String::from("includes")); } } - self.file.seek(std::io::SeekFrom::Start(0)).unwrap(); - self.file.write_all(dot::Dot::new(&self.graph).to_string().as_bytes()).unwrap(); - self.file.flush().unwrap(); - self.file.seek(std::io::SeekFrom::Start(0)).unwrap(); + /* self.file.seek(std::io::SeekFrom::Start(0))?; + self.file.write_all(dot::Dot::new(&self.graph).to_string().as_bytes())?; + self.file.flush()?; + self.file.seek(std::io::SeekFrom::Start(0))?; */ + + eprintln!("finished building project include graph"); + std::thread::sleep(std::time::Duration::from_secs(1)); + self.endpoint.send_notification("status", vec!["$(check)", "Finished building project!"]).unwrap(); + std::thread::sleep(std::time::Duration::from_secs(3)); + self.endpoint.send_notification("clearStatus", None::<()>).unwrap(); } pub fn find_includes(&self, root: &str, file: &str) -> Vec { @@ -157,7 +169,8 @@ impl LanguageServerHandling for MinecraftShaderLanguageServer { fn workspace_change_configuration(&mut self, _: DidChangeConfigurationParams) {} fn did_open_text_document(&mut self, _: DidOpenTextDocumentParams) {} - fn did_change_text_document(&mut self, _: DidChangeTextDocumentParams) {} + fn did_change_text_document(&mut self, params: DidChangeTextDocumentParams) { + } fn did_close_text_document(&mut self, _: DidCloseTextDocumentParams) {} fn did_save_text_document(&mut self, _: DidSaveTextDocumentParams) {} fn did_change_watched_files(&mut self, _: DidChangeWatchedFilesParams) {}