11"use strict" ;
22
3- import { ConfigurationChangeEvent , ExtensionContext , commands , window , workspace } from "vscode" ;
3+ import { ExtensionContext , commands , window , workspace } from "vscode" ;
44import { LanguageClient , ServerOptions } from "vscode-languageclient/node" ;
55import { promisify } from "util" ;
66import { exec } from "child_process" ;
@@ -10,49 +10,85 @@ import Visualize from "./Visualize";
1010
1111const promiseExec = promisify ( exec ) ;
1212
13+ // This is the expected top-level export that is called by VSCode.
1314export function activate ( context : ExtensionContext ) {
15+ // This output channel is going to contain all of our informational messages.
16+ // It's not really meant for the end-user, it's more for debugging.
1417 const outputChannel = window . createOutputChannel ( "Syntax Tree" ) ;
18+
19+ // These objects will get initialized once the language client is ready.
1520 let languageClient : LanguageClient | null = null ;
1621 let visualizer : Visualize | null = null ;
1722
23+ // This is the list of objects that implement the Disposable interface. They
24+ // will all get cleaned up with this extension is deactivated. It's important
25+ // to add them to this list so we don't leak memory.
1826 context . subscriptions . push (
27+ // The output channel itself is a disposable. When the extension is
28+ // deactivated it will be removed from the list.
1929 outputChannel ,
30+
31+ // Each of the commands that interacts with this extension is a disposable.
32+ // It's important to register them here as opposed to whenever the client
33+ // starts up because we don't want to register them again whenever the
34+ // client restarts.
2035 commands . registerCommand ( "syntaxTree.start" , startLanguageServer ) ,
2136 commands . registerCommand ( "syntaxTree.stop" , stopLanguageServer ) ,
2237 commands . registerCommand ( "syntaxTree.restart" , restartLanguageServer ) ,
2338 commands . registerCommand ( "syntaxTree.visualize" , ( ) => visualizer ?. visualize ( ) ) ,
2439 commands . registerCommand ( "syntaxTree.showOutputChannel" , ( ) => outputChannel . show ( ) ) ,
25- workspace . onDidChangeConfiguration ( event =>
26- event . affectsConfiguration ( "syntaxTree" ) &&
27- restartLanguageServer ( ) )
40+ workspace . onDidChangeConfiguration ( event => {
41+ if ( event . affectsConfiguration ( "syntaxTree" ) ) {
42+ restartLanguageServer ( ) ;
43+ }
44+ } )
2845 ) ;
2946
47+ // We're returning a Promise from this function that will start the Ruby
48+ // subprocess.
3049 return startLanguageServer ( ) ;
3150
51+ // This function is called when the extension is activated or when the
52+ // language server is restarted.
3253 async function startLanguageServer ( ) {
54+ // The top-level configuration group is syntaxTree. All of the configuration
55+ // for the extension is under that group.
3356 const config = workspace . getConfiguration ( "syntaxTree" ) ;
34- const addlPlugins = config . get < string [ ] > ( "additionalPlugins" ) || [ ] ;
35- const singleQuotes = config . get < boolean > ( "singleQuotes" ) ;
36- const trailingComma = config . get < boolean > ( "trailingComma" ) ;
3757
58+ // The args are going to be passed to the stree executable. It's important
59+ // that it lines up with what the CLI expects.
3860 const args = [ "lsp" ] ;
39-
4061 const plugins = new Set < string > ( ) ;
41- if ( singleQuotes ) {
62+
63+ if ( config . get < boolean > ( "singleQuotes" ) ) {
4264 plugins . add ( "plugin/single_quotes" ) ;
4365 }
44- if ( trailingComma ) {
66+
67+ if ( config . get < boolean > ( "trailingComma" ) ) {
4568 plugins . add ( "plugin/trailing_comma" ) ;
4669 }
47- addlPlugins . forEach ( plugins . add ) ;
4870
49- if ( plugins . size ) {
71+ const additionalPlugins = config . get < string [ ] > ( "additionalPlugins" ) ;
72+ if ( additionalPlugins ) {
73+ additionalPlugins . forEach ( plugin => plugins . add ( plugin ) ) ;
74+ }
75+
76+ // If there are any plugins, then we'll pass the --plugins command line
77+ // option to the stree lsp command.
78+ if ( plugins . size > 0 ) {
5079 args . push ( `--plugins=${ Array . from ( plugins ) . join ( "," ) } ` ) ;
5180 }
5281
5382 outputChannel . appendLine ( `Starting language server with ${ plugins . size } plugin(s)...` ) ;
5483 let run : ServerOptions = { command : "stree" , args } ;
5584
85+ // There's a bit of complexity here. Basically, if there's an open folder,
86+ // then w're going to check if the syntax_tree gem is inside the bundle. If
87+ // it is, then we'll run bundle exec stree. This is good, because it'll
88+ // ensure that we get the correct version of the gem. If it's not in the
89+ // bundle or there is no bundle, then we'll just run the global stree. This
90+ // might be correct in the end if the right environment variables are set,
91+ // but it's a bit of a prayer.
5692 if ( workspace . workspaceFolders ) {
5793 const cwd = workspace . workspaceFolders ! [ 0 ] . uri . fsPath ;
5894
@@ -64,30 +100,42 @@ export function activate(context: ExtensionContext) {
64100 }
65101 }
66102
103+ // Here, we instantiate the language client. This is the object that is
104+ // responsible for the communication and management of the Ruby subprocess.
67105 languageClient = new LanguageClient ( "Syntax Tree" , { run, debug : run } , {
68106 documentSelector : [
69107 { scheme : "file" , language : "ruby" } ,
70108 ] ,
71109 outputChannel
72110 } ) ;
73111
112+ // Here we're going to wait for the language server to start.
74113 context . subscriptions . push ( languageClient . start ( ) ) ;
75114 await languageClient . onReady ( ) ;
76115
116+ // Finally, now that the language server has been properly started, we can
117+ // add the various features to the extension. Each of them in turn
118+ // implements Disposable so that they clean up their own resources.
77119 visualizer = new Visualize ( languageClient , outputChannel ) ;
78120 context . subscriptions . push (
79121 new InlayHints ( languageClient , outputChannel ) ,
80122 visualizer
81123 ) ;
82124 }
83125
126+ // This function is called as part of the shutdown or restart process. It's
127+ // always user-initiated either through manually executing an action or
128+ // changing some configuration.
84129 async function stopLanguageServer ( ) {
85130 if ( languageClient ) {
86131 outputChannel . appendLine ( "Stopping language server..." ) ;
87132 await languageClient . stop ( ) ;
88133 }
89134 }
90135
136+ // This function is called as part of the restart process. Like
137+ // stopLanguageServer, it's always user-initiated either through manually
138+ // executing an action or changing some configuration.
91139 async function restartLanguageServer ( ) {
92140 outputChannel . appendLine ( "Restarting language server..." ) ;
93141 await stopLanguageServer ( ) ;
0 commit comments