char-slop/vscode-hubris
git clone https://git.t4t.associates/char-slop/vscode-hubris
f4210c2
main
1const vscode = require ( "vscode" ); 2const { execFile} = require ( "node:child_process" ); 3const fs = require ( "node:fs/promises" ); 4const path = require ( "node:path" ); 5const { promisify} = require ( "node:util" ); 6 7const exec = promisify ( execFile ); 8 9function activate ( extension ) { 10const folders = vscode . workspace . workspaceFolders ?? []; 11if ( ! vscode . workspace . isTrusted || folders . length !== 1 ) return ; 12const folder = folders [ 0 ]; 13const output = vscode . window . createOutputChannel ( "Hubris" ); 14const status = vscode . window . createStatusBarItem ( vscode . StatusBarAlignment . Left ); 15status . command = "hubris.refresh" ; 16extension . subscriptions . push ( output , status ); 17const targetFile = path . join ( extension . storageUri . fsPath , "target" ); 18let applied ; 19let requested ; 20let running = false ; 21let timer ; 22let disposed = false ; 23let child ; 24 25function request () { 26const document = vscode . window . activeTextEditor ?. document ; 27const app = vscode . workspace . getConfiguration ( "hubris" , folder . uri ). get ( "app" ); 28if ( ! app || ! document || document . languageId !== "rust" || document . uri . scheme !== "file" ) return ; 29const relative = path . relative ( folder . uri . fsPath , document . uri . fsPath ); 30if ( relative . startsWith ( `.. ${ path . sep } ` ) || path . isAbsolute ( relative )) return ; 31requested = { file :document . uri . fsPath , app}; 32clearTimeout ( timer ); 33timer = setTimeout ( update , 250 ); 34} 35 36async function update () { 37if ( running || disposed ) return ; 38running = true ; 39try { 40while ( requested && ! disposed ) { 41const { file, app} = requested ; 42requested = undefined ; 43const args = [ "xtask" , "lsp" ]; 44if ( applied ?. app === app ) args . push ( "-c" , JSON . stringify ({ toml :app , task :applied . task })); 45args . push ( file ); 46const env = { ...process . env , HUBRIS_APP :app }; 47delete env . HUBRIS_TASK ; 48const job = exec ( "cargo" , args , { cwd :folder . uri . fsPath , env, maxBuffer :4 * 1024 * 1024 }); 49child = job . child ; 50const { stdout} = await job ; 51child = undefined ; 52if ( disposed || ( requested && ( requested . file !== file || requested . app !== app ))) continue ; 53const result = JSON . parse ( stdout ); 54if ( result . Err !== undefined ) { 55status . text = "$(warning) Hubris: no task" ; 56status . tooltip = result . Err ; 57status . show (); 58output . appendLine ( ` ${ file } : ${ result . Err } ` ); 59continue ; 60} 61const next = result . Ok ; 62if ( ! next ?. app || ! next . task || ! Array . isArray ( next . buildOverrideCommand )) { 63throw new Error ( "Unexpected response from cargo xtask lsp" ); 64} 65const label = ` ${ next . app } : ${ next . task } ` ; 66status . text = `Hubris: ${ label } ` ; 67status . tooltip = "Rust context for the active file; click to refresh" ; 68status . show (); 69if ( JSON . stringify ( next ) === JSON . stringify ( applied )) continue ; 70 71status . text = `$(sync~spin) Hubris: ${ label } ` ; 72await vscode . commands . executeCommand ( "rust-analyzer.stopServer" ); 73try { 74// Keep server.extraEnv stable: changing it prompts for a restart on every switch. 75await fs . mkdir ( extension . storageUri . fsPath , { recursive :true }); 76await fs . writeFile ( targetFile , label ); 77const config = vscode . workspace . getConfiguration ( "rust-analyzer" , folder . uri ); 78const settings = { 79"server.path" :extension . asAbsolutePath ( "rust-analyzer" ), 80"server.extraEnv" :{ 81 ...config . get ( "server.extraEnv" ), 82HUBRIS_WORKSPACE_ROOT :folder . uri . fsPath , 83HUBRIS_CONTEXT_FILE :targetFile , 84}, 85"cargo.buildScripts.overrideCommand" :next . buildOverrideCommand , 86}; 87for ( const [ key , value ] of Object . entries ( settings )) { 88if ( JSON . stringify ( config . get ( key )) !== JSON . stringify ( value )) { 89await config . update ( key , value , vscode . ConfigurationTarget . Workspace ); 90} 91} 92} finally { 93if ( ! disposed ) await vscode . commands . executeCommand ( "rust-analyzer.startServer" ); 94} 95applied = next ; 96status . text = `Hubris: ${ label } ` ; 97output . appendLine ( `Context: ${ label } ( ${ file } )` ); 98} 99} catch ( error ) { 100if ( ! disposed ) { 101output . appendLine ( error . stack ?? String ( error )); 102status . text = "$(warning) Hubris" ; 103status . show (); 104vscode . window . showErrorMessage ( `Hubris context switch failed: ${ error . message } ` ); 105} 106} finally { 107running = false ; 108if ( requested && ! disposed ) timer = setTimeout ( update , 250 ); 109} 110} 111 112extension . subscriptions . push ( 113vscode . window . onDidChangeActiveTextEditor ( request ), 114vscode . workspace . onDidSaveTextDocument (( document ) => { 115if ( document . uri . fsPath . endsWith ( ".toml" ) || path . basename ( document . uri . fsPath ) === "Cargo.lock" ) request (); 116}), 117vscode . workspace . onDidChangeConfiguration (( event ) => { 118if ( event . affectsConfiguration ( "hubris.app" )) request (); 119}), 120vscode . commands . registerCommand ( "hubris.refresh" , request ), 121{ dispose () { disposed = true ; clearTimeout ( timer ); child ?. kill (); } }, 122); 123request (); 124} 125 126module . exports = { activate};