77package viper .server .frontends .lsp
88
99import ch .qos .logback .classic .Logger
10- import org .eclipse .lsp4j .DocumentSymbol
10+ import org .eclipse .lsp4j .Range
1111import viper .server .core .VerificationExecutionContext
1212import viper .server .frontends .lsp .VerificationState .Ready
1313
1414import java .util .concurrent .{ConcurrentHashMap , ConcurrentMap }
15- import scala .collection .mutable .ArrayBuffer
1615import scala .concurrent .Future
1716import scala .jdk .CollectionConverters ._
1817import scala .jdk .FutureConverters ._
18+ import viper .server .frontends .lsp .file .FileManager
19+ import viper .server .frontends .lsp .file .VerificationManager
1920
2021/** manages per-client state and interacts with the server instance (which is shared among all clients) */
2122class ClientCoordinator (val server : ViperServerService )(implicit executor : VerificationExecutionContext ) {
@@ -46,59 +47,41 @@ class ClientCoordinator(val server: ViperServerService)(implicit executor: Verif
4647 ! _exited
4748 }
4849
49- def addFileIfNecessary (uri : String ): Unit = {
50- logger.trace(s " Adding FileManager for $uri if it does not exist yet " )
51- val coordinator = this
52- val createFMFunc = new java.util.function.Function [String , FileManager ]() {
53- override def apply (t : String ): FileManager = {
54- logger.trace(s " FileManager created for $uri" )
55- new FileManager (coordinator, uri)
56- }
50+ def closeFile (uri : String ): Unit = {
51+ val toRemove = Option (_files.get(uri)).map(fm => {
52+ fm.isOpen = false
53+ fm.removeDiagnostics()
54+ fm.isRoot
55+ }).getOrElse(false )
56+ if (toRemove) {
57+ logger.trace(s " Removing FileManager for $uri" )
58+ _files.remove(uri)
5759 }
58- // we use `computeIfAbsent` instead of `putIfAbsent` such that a new FileManager is only created if it's absent
59- _files.computeIfAbsent(uri, createFMFunc)
60- }
61-
62- def removeFileIfExists (uri : String ): Unit = {
63- logger.trace(s " Removing FileManager for $uri" )
64- _files.remove(uri)
65- }
66-
67- /** clears definitions and symbols associated with a file */
68- def resetFile (uri : String ): Unit = {
69- Option (_files.get(uri))
70- .foreach(fm => {
71- fm.symbolInformation = ArrayBuffer .empty
72- fm.definitions = ArrayBuffer .empty
73- })
7460 }
7561
7662 def resetDiagnostics (uri : String ): Unit = {
77- Option (_files.get(uri))
78- .foreach(fm => fm.resetDiagnostics())
63+ getFileManager(uri).removeDiagnostics()
7964 }
8065
81- def getSymbolsForFile (uri : String ): Array [DocumentSymbol ]= {
82- Option (_files.get(uri))
83- .map(fm => fm.symbolInformation.toArray)
84- .getOrElse(Array .empty)
85- }
86-
87- def getDefinitionsForFile (uri : String ): ArrayBuffer [Definition ] = {
88- Option (_files.get(uri))
89- .map(fm => fm.definitions)
90- .getOrElse(ArrayBuffer .empty)
66+ def handleChange (uri : String , range : Range , text : String ): Unit = {
67+ val fm = getFileManager(uri)
68+ fm.synchronized {
69+ fm.handleContentChange(range, text)
70+ }
9171 }
9272
9373 /** Checks if verification can be started for a given file.
9474 *
9575 * Informs client differently depending on whether or not verification attempt was triggered manually
9676 * */
97- def canVerificationBeStarted (uri : String , manuallyTriggered : Boolean ): Boolean = {
77+ def canVerificationBeStarted (uri : String , content : String , manuallyTriggered : Boolean ): Boolean = {
9878 logger.trace(" canVerificationBeStarted" )
9979 if (server.isRunning) {
10080 logger.trace(" server is running" )
101- addFileIfNecessary(uri)
81+ // This should only be necessary if one wants to verify a closed file for some reason
82+ val fm = getFileManager(uri, Some (content))
83+ // This will be the new project root
84+ makeEmptyRoot(fm)
10285 true
10386 } else {
10487 logger.trace(" server is not running" )
@@ -111,16 +94,15 @@ class ClientCoordinator(val server: ViperServerService)(implicit executor: Verif
11194 }
11295
11396 def stopRunningVerification (uri : String ): Future [Boolean ] = {
114- Option (_files.get(uri))
115- .map(fm => fm.stopVerification()
116- .map(_ => {
117- logger.trace(s " stopVerification has completed for ${fm.uri}" )
118- val params = StateChangeParams (Ready .id, verificationCompleted = 0 , verificationNeeded = 0 , uri = uri)
119- sendStateChangeNotification(params, Some (fm))
120- true
121- })
122- .recover(_ => false ))
123- .getOrElse(Future .successful(false ))
97+ val fm = getFileManager(uri)
98+ fm.stop()
99+ .map(_ => {
100+ logger.trace(s " stopVerification has completed for ${fm.file.uri}" )
101+ val params = StateChangeParams (Ready .id, verificationCompleted = 0 , verificationNeeded = 0 , uri = uri)
102+ sendStateChangeNotification(params, Some (fm))
103+ true
104+ })
105+ .recover(_ => false )
124106 }
125107
126108 /** Stops all running verifications.
@@ -130,18 +112,26 @@ class ClientCoordinator(val server: ViperServerService)(implicit executor: Verif
130112 * */
131113 def stopAllRunningVerifications (): Future [Unit ] = {
132114 val tasks = _files.values().asScala.map(fm =>
133- fm.stopVerification ().map(_ => {
134- logger.trace(s " stopVerification has completed for ${fm.uri}" )
115+ fm.stop ().map(_ => {
116+ logger.trace(s " stopVerification has completed for ${fm.file. uri}" )
135117 }))
136118 Future .sequence(tasks).map(_ => {
137119 logger.debug(" all running verifications have been stopped" )
138120 })
139121 }
140122
141123 /** returns true if verification was started */
142- def startVerification (backendClassName : String , customArgs : String , uri : String , manuallyTriggered : Boolean ): Boolean = {
143- Option (_files.get(uri))
144- .exists(fm => fm.startVerification(backendClassName, customArgs, manuallyTriggered))
124+ def startVerification (backendClassName : String , customArgs : String , uri : String , manuallyTriggered : Boolean ): Future [Boolean ] = {
125+ val fm = getFileManager(uri)
126+ fm.startVerification(backendClassName, customArgs, fm.content, manuallyTriggered)
127+ }
128+
129+ /** returns true if parse/typecheck was started */
130+ def startParseTypecheck (uri : String ): Boolean = {
131+ val fm = getFileManager(uri)
132+ val project = Option (_files.get(uri)).flatMap(_.projectRoot).getOrElse(uri)
133+ val root = getFileManager(project)
134+ root.runParseTypecheck(fm.content)
145135 }
146136
147137 /** flushes verification cache, optionally only for a particular file */
@@ -173,9 +163,9 @@ class ClientCoordinator(val server: ViperServerService)(implicit executor: Verif
173163 *
174164 * If state change is related to a particular file, its manager's state is also updated.
175165 * */
176- def sendStateChangeNotification (params : StateChangeParams , task : Option [FileManager ]): Unit = {
166+ def sendStateChangeNotification (params : StateChangeParams , task : Option [VerificationManager ]): Unit = {
177167 // update file manager's state:
178- task.foreach(fm => fm .state = VerificationState (params.newState))
168+ task.foreach(vm => vm .state = VerificationState (params.newState))
179169 try {
180170 client.notifyStateChanged(params)
181171 } catch {
@@ -204,4 +194,48 @@ class ClientCoordinator(val server: ViperServerService)(implicit executor: Verif
204194 if (! isAlive) return
205195 client.notifyHint(HintMessage (message, showSettingsButton, showViperToolsUpdateButton ))
206196 }
197+
198+ private def getFileManager (uri : String , content : Option [String ] = None ): FileManager = {
199+ var createdNew = false
200+ val coordinator = this
201+ val createFMFunc = new java.util.function.Function [String , FileManager ]() {
202+ override def apply (t : String ): FileManager = {
203+ logger.trace(s " FileManager created for $uri" )
204+ createdNew = true
205+ FileManager (uri, coordinator, content)
206+ }
207+ }
208+ // we use `computeIfAbsent` instead of `putIfAbsent` such that a new FileManager is only created if it's absent
209+ val fm = _files.computeIfAbsent(uri, createFMFunc)
210+ // Override the content if we are given one and the file manager was not just created
211+ if (! createdNew && content.isDefined) fm.content.set(content.get)
212+ fm
213+ }
214+ def ensureFmExists (uri : String , content : String ): FileManager = {
215+ getFileManager(uri, Some (content))
216+ }
217+ def getRoot (uri : String ): FileManager = {
218+ val fm = getFileManager(uri)
219+ fm.projectRoot.map(getFileManager(_)).getOrElse(fm)
220+ }
221+
222+ // /////////////////////
223+ // Project management
224+ // /////////////////////
225+
226+ def addToProject (uri : String , root : String , getContents : Boolean ): (Option [String ], Option [Set [String ]]) = {
227+ getFileManager(uri).addToProject(root, getContents)
228+ }
229+ def removeFromProject (uri : String , root : String ) = {
230+ Option (_files.get(uri)).map(_.removeFromProject(root))
231+ }
232+ def makeEmptyRoot (fm : FileManager ) = {
233+ for (leaves <- fm.projectLeaves; leaf <- leaves) {
234+ removeFromProject(leaf, fm.file_uri)
235+ }
236+ fm.project = Left (Map ())
237+ }
238+ def handleChangeInLeaf (root : String , leaf : String , range : Range , text : String ): Unit = {
239+ Option (_files.get(root)).map(_.handleChangeInLeaf(leaf, range, text))
240+ }
207241}
0 commit comments