@@ -42,7 +42,6 @@ let documents: TextDocuments = new TextDocuments();
4242// for open, change and close text document events
4343documents . listen ( connection ) ;
4444
45-
4645// After the server has started the client sends an initialize request. The server receives
4746// in the passed params the rootPath of the workspace plus the client capabilities.
4847let workspaceRoot : string ;
@@ -72,9 +71,19 @@ connection.onInitialize((params): InitializeResult => {
7271// The content of a text document has changed. This event is emitted
7372// when the text document first opened or when its content has changed.
7473documents . onDidChangeContent ( ( doc ) => {
74+ //connection.console.log("SERVER onDidChangeContent: " + doc.document.uri); //debug
7575 validateTextDocument ( doc . document ) ;
7676} ) ;
7777
78+ documents . onDidOpen ( ( doc ) => {
79+ //connection.console.log("SERVER onDidOpen: " + doc.document.uri); //debug
80+ } ) ;
81+
82+ documents . onDidClose ( ( doc ) => {
83+ //connection.console.log("SERVER onDidClose: " + doc.document.uri); //debug
84+ closeTextDocument ( doc . document ) ;
85+ } ) ;
86+
7887// The settings interface describes the server relevant settings part
7988interface Settings {
8089 composer : ComposerSettings ;
@@ -236,7 +245,7 @@ function handleGenerateUml(diagramTitle: string, originatingFileName: string) {
236245
237246/**
238247 * Main method driven by the LSP when the user opens or changes a cto, acl or qry file
239- * @param {string } textDocument - ".cto", "permissions.acl" or ".qry" document from the client to validate
248+ * @param {TextDocument } textDocument - ".cto", "permissions.acl" or ".qry" document from the client to validate
240249 */
241250function validateTextDocument ( textDocument : TextDocument ) : void {
242251 let langId = textDocument . languageId ; //type of file we are processing
@@ -268,52 +277,102 @@ function validateTextDocument(textDocument: TextDocument): void {
268277 validateExistingQueryModelFile ( queryFile ) ;
269278 }
270279 }
280+ } else {
281+ //clear any errors on the empty document
282+ sendDiagnosticSuccess ( textDocument . uri ) ; //all OK
271283 }
272284
273285}
274286
275287/**
276288 * Validates a cto file that the user has just opened or changed in the workspace.
277- * @param {string } textDocument - ".cto" file to validate
289+ * @param {TextDocument } textDocument - ".cto" file to validate
278290 * @private
279291 */
280292function validateCtoModelFile ( textDocument : TextDocument ) : void {
281293 try {
294+ //debug
295+ //var allNS = modelManager.getNamespaces();
296+ //connection.console.log("SERVER DOC-LEN: " + textDocument.getText().length); //debug
297+ //connection.console.log("SERVER ALL-NS: " + allNS.length); //debug
298+ //allNS.forEach(ns => {
299+ //connection.console.log("SERVER NS: " + ns); //debug
300+ //});
301+
282302 //hack to workaround circularly dependent documents
283- let currentModels = [ ] ;
284- documents . all ( ) . forEach ( ( textDocument : TextDocument ) => {
285- if ( textDocument . languageId == "composer" ) {
286- let model = new ModelFile ( modelManager , textDocument . getText ( ) , textDocument . uri ) ;
287- if ( ! modelManager . getModelFile ( model . getNamespace ( ) ) ) {
288- //only add if not existing
289- currentModels . push ( model ) ;
303+ let currentModels = [ ] ;
304+ documents . all ( ) . forEach ( ( doc : TextDocument ) => {
305+ if ( doc . languageId == "composer" ) {
306+ let modelContents = doc . getText ( ) ; //*.cto file
307+ if ( modelContents . length > 0 ) {
308+ //cannot add 0 length documents
309+ try {
310+ let model : any = new ModelFile ( modelManager , modelContents , doc . uri ) ;
311+ model . lineCount = doc . lineCount ; //store the count so future errors have access
312+ if ( ! modelManager . getModelFile ( model . getNamespace ( ) ) ) {
313+ //only add if not existing and no error
314+ currentModels . push ( model ) ;
315+ }
316+ } catch ( err ) {
317+ //we had an error creating the model - output the error now
318+ //connection.console.log("SERVER model err early: " + err.toString() + " " + doc.uri); //debug
319+ buildAndSendDiagnosticFromException ( err , doc . lineCount , doc . uri ) ;
320+ }
290321 }
291322 }
292323 } ) ;
293- //connection.console.log("SERVER addModelFiles: " + currentModels.length); //debug
294- modelManager . addModelFiles ( currentModels ) ;
295324
325+ //connection.console.log("SERVER addModelFiles: " + currentModels.length); //debug
326+ if ( currentModels . length > 0 ) {
327+ //only add if we have files or it forces a validation too early!
328+ try {
329+ modelManager . addModelFiles ( currentModels ) ;
330+ } catch ( err ) {
331+ //one of the files had an error validating, but the exception does not tell us which one so we must,
332+ //ignore errors adding files here and wait until the user selects the file in error to report it.
333+ //connection.console.log("SERVER model err adding: " + err.toString()); //debug
334+ }
335+ }
296336
297337 let modelContents = textDocument . getText ( ) ; //*.cto file
298338 //add or update, depending on existance. ModelFile and modelManager calls may throw an exception
299- let model = new ModelFile ( modelManager , modelContents , textDocument . uri ) ;
300- if ( modelManager . getModelFile ( model . getNamespace ( ) ) ) {
339+ let model : any = new ModelFile ( modelManager , modelContents , textDocument . uri ) ;
340+ model . lineCount = textDocument . lineCount ; //store the count so future errors have access
341+ var existingModel = modelManager . getModelFile ( model . getNamespace ( ) ) ;
342+ if ( existingModel && existingModel . getName ( ) === textDocument . uri ) {
343+ //update if we have a file that matches an existing namespace and filename
301344 //connection.console.log("SERVER update model: " + model.getNamespace()); //debug
302345 modelManager . updateModelFile ( model ) ;
303346 } else {
347+ //Composer does not allow two different files to belong to the same namespace, in which
348+ //case addModelFile() will throw for us when we add the second instance.
349+ //note, if we get here it will be because the file has an error or it would have been added above.
304350 //connection.console.log("SERVER add model: " + model.getNamespace()); //debug
305351 modelManager . addModelFile ( model ) ;
306352 }
307- sendDiagnosticSuccess ( textDocument . uri ) ; //all OK
353+
354+ //finally check valiatation for cross validation for all additions and changes against other open models
355+ modelManager . getModelFiles ( ) . forEach ( model => {
356+ try {
357+ model . validate ( ) ;
358+ //clear any existing open errors against the file
359+ sendDiagnosticSuccess ( model . getName ( ) ) ; //the name is the uri
360+ } catch ( err ) {
361+ //report any errors against the file
362+ buildAndSendDiagnosticFromException ( err , model . lineCount , model . getName ( ) ) ;
363+ }
364+ } ) ;
365+
366+ sendDiagnosticSuccess ( textDocument . uri ) ; //all OK for current document - probably unnecessary to report it again...
308367 } catch ( err ) {
309- //connection.console.log("SERVER model err: " + err.toString()); //debug
368+ //connection.console.log("SERVER model err: " + err.toString() + " " + textDocument.uri ); //debug
310369 buildAndSendDiagnosticFromException ( err , textDocument . lineCount , textDocument . uri ) ;
311370 }
312371}
313372
314373/**
315374 * Validates an acl file that the user has just opened or changed in the workspace.
316- * @param {string } textDocument - new "permissions.acl" file to validate
375+ * @param {TextDocument } textDocument - new "permissions.acl" file to validate
317376 * @private
318377 */
319378function validateNewAclModelFile ( textDocument : TextDocument ) : void {
@@ -345,7 +404,7 @@ function validateExistingAclModelFile(aclFile): void {
345404
346405/**
347406 * Validates a qry file that the user has just opened or changed in the workspace.
348- * @param {string } textDocument - new ".qry" file to validate
407+ * @param {TextDocument } textDocument - new ".qry" file to validate
349408 * @private
350409 */
351410function validateNewQueryModelFile ( textDocument : TextDocument ) : void {
@@ -394,7 +453,7 @@ function buildAndSendDiagnosticFromException(err, lineCount: number, sourceURI:
394453
395454 //if it's a cto composer exception it will have a short message, but acl and qry ones do not
396455 if ( typeof err . getShortMessage === "function" ) {
397- //Short msg does not have and file and line info which is what we want
456+ //Short msg does not have any file and line info which is what we want
398457 fullMsg += err . getShortMessage ( ) ;
399458 } else {
400459 //May have file and line info
@@ -465,6 +524,73 @@ function sendDiagnosticSuccess(sourceURI: string): void {
465524 connection . sendDiagnostics ( { uri : sourceURI , diagnostics } ) ;
466525}
467526
527+ /**
528+ * Main method driven by the LSP when the user closes a cto, acl or qry file
529+ * @param {TextDocument } textDocument - ".cto", "permissions.acl" or ".qry" document from the client to close
530+ */
531+ function closeTextDocument ( textDocument : TextDocument ) : void {
532+ let langId = textDocument . languageId ; //type of file we are processing
533+ //note - this is the FULL document text as we can't do incremental yet!
534+ let txt = textDocument . getText ( ) ;
535+
536+ //only care about files with data
537+ if ( txt != null && txt . length > 0 ) {
538+ //different behaviour for each language type
539+ if ( langId == "composer-acl" ) {
540+ //permissions.acl file
541+ //TODO - close acl file
542+ } else if ( langId == "composer-qry" ) {
543+ //.qry file
544+ //TODO - close query file
545+ } else {
546+ //raw composer .cto file
547+ try {
548+ //It is possible the model file could have unparsable errors but will
549+ //handle this case another day (requires map of open files)
550+ let model : any = new ModelFile ( modelManager , txt , textDocument . uri ) ;
551+ var existingModel = modelManager . getModelFile ( model . getNamespace ( ) ) ;
552+ if ( existingModel && existingModel . getName ( ) === textDocument . uri ) {
553+ //only delete if we match on namespace and name
554+ modelManager . deleteModelFile ( model . getNamespace ( ) ) ;
555+ } else {
556+ //clear any existing errors on the closed document, otherwise they are ophaned
557+ sendDiagnosticSuccess ( textDocument . uri ) ; //all OK
558+ }
559+
560+ //finally check valiatation for cross validation against other open models
561+ modelManager . getModelFiles ( ) . forEach ( currrentModel => {
562+ try {
563+ currrentModel . validate ( ) ;
564+ //clear any existing open errors against the file
565+ sendDiagnosticSuccess ( currrentModel . getName ( ) ) ; //the name is the uri
566+ } catch ( err ) {
567+ //report any errors against the still open file
568+ buildAndSendDiagnosticFromException ( err , currrentModel . lineCount , currrentModel . getName ( ) ) ;
569+ }
570+ } ) ;
571+ } catch ( err ) {
572+ //ignore errors as we are closing the file.
573+ //connection.console.log("SERVER close err: " + err.toString() + " " + textDocument.uri); //debug
574+ }
575+ //if we have an acl file we should revalidate it incase the model changes broke something
576+ const aclFile = aclManager . getAclFile ( ) ;
577+ if ( aclFile != null ) {
578+ validateExistingAclModelFile ( aclFile ) ;
579+ }
580+
581+ //if we have a query file we should revalidate it incase the model changes broke something
582+ const queryFile = queryManager . getQueryFile ( ) ;
583+ if ( queryFile != null ) {
584+ validateExistingQueryModelFile ( queryFile ) ;
585+ }
586+ }
587+ } else {
588+ //clear any errors on the empty document
589+ sendDiagnosticSuccess ( textDocument . uri ) ; //all OK
590+ }
591+
592+ }
593+
468594connection . onDidChangeWatchedFiles ( ( change ) => {
469595 // Monitored files have change in VSCode
470596 //connection.console.log('We received a file change event');
0 commit comments