77package viper .server .frontends .lsp
88
99import ch .qos .logback .classic .Logger
10- import org .eclipse .lsp4j .Range
10+ import org .eclipse .lsp4j .DocumentSymbol
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
1516import scala .concurrent .Future
1617import scala .jdk .CollectionConverters ._
1718import scala .jdk .FutureConverters ._
18- import viper .server .frontends .lsp .file .FileManager
19- import viper .server .frontends .lsp .file .VerificationManager
2019
2120/** manages per-client state and interacts with the server instance (which is shared among all clients) */
2221class ClientCoordinator (val server : ViperServerService )(implicit executor : VerificationExecutionContext ) {
@@ -47,41 +46,59 @@ class ClientCoordinator(val server: ViperServerService)(implicit executor: Verif
4746 ! _exited
4847 }
4948
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)
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+ }
5957 }
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+ })
6074 }
6175
6276 def resetDiagnostics (uri : String ): Unit = {
63- getFileManager(uri).removeDiagnostics()
77+ Option (_files.get(uri))
78+ .foreach(fm => fm.resetDiagnostics())
6479 }
6580
66- def handleChange (uri : String , range : Range , text : String ): Unit = {
67- val fm = getFileManager(uri)
68- fm.synchronized {
69- fm.handleContentChange(range, text)
70- }
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)
7191 }
7292
7393 /** Checks if verification can be started for a given file.
7494 *
7595 * Informs client differently depending on whether or not verification attempt was triggered manually
7696 * */
77- def canVerificationBeStarted (uri : String , content : String , manuallyTriggered : Boolean ): Boolean = {
97+ def canVerificationBeStarted (uri : String , manuallyTriggered : Boolean ): Boolean = {
7898 logger.trace(" canVerificationBeStarted" )
7999 if (server.isRunning) {
80100 logger.trace(" server is running" )
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)
101+ addFileIfNecessary(uri)
85102 true
86103 } else {
87104 logger.trace(" server is not running" )
@@ -94,15 +111,16 @@ class ClientCoordinator(val server: ViperServerService)(implicit executor: Verif
94111 }
95112
96113 def stopRunningVerification (uri : String ): Future [Boolean ] = {
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 )
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 ))
106124 }
107125
108126 /** Stops all running verifications.
@@ -112,26 +130,18 @@ class ClientCoordinator(val server: ViperServerService)(implicit executor: Verif
112130 * */
113131 def stopAllRunningVerifications (): Future [Unit ] = {
114132 val tasks = _files.values().asScala.map(fm =>
115- fm.stop ().map(_ => {
116- logger.trace(s " stopVerification has completed for ${fm.file. uri}" )
133+ fm.stopVerification ().map(_ => {
134+ logger.trace(s " stopVerification has completed for ${fm.uri}" )
117135 }))
118136 Future .sequence(tasks).map(_ => {
119137 logger.debug(" all running verifications have been stopped" )
120138 })
121139 }
122140
123141 /** returns true if verification was started */
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)
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))
135145 }
136146
137147 /** flushes verification cache, optionally only for a particular file */
@@ -163,9 +173,9 @@ class ClientCoordinator(val server: ViperServerService)(implicit executor: Verif
163173 *
164174 * If state change is related to a particular file, its manager's state is also updated.
165175 * */
166- def sendStateChangeNotification (params : StateChangeParams , task : Option [VerificationManager ]): Unit = {
176+ def sendStateChangeNotification (params : StateChangeParams , task : Option [FileManager ]): Unit = {
167177 // update file manager's state:
168- task.foreach(vm => vm .state = VerificationState (params.newState))
178+ task.foreach(fm => fm .state = VerificationState (params.newState))
169179 try {
170180 client.notifyStateChanged(params)
171181 } catch {
@@ -194,48 +204,4 @@ class ClientCoordinator(val server: ViperServerService)(implicit executor: Verif
194204 if (! isAlive) return
195205 client.notifyHint(HintMessage (message, showSettingsButton, showViperToolsUpdateButton ))
196206 }
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- }
241207}
0 commit comments