11package completions.lsp
22
3+ import completions.dto.api.CompletionRequest
4+ import completions.dto.api.ProjectFile
35import completions.exceptions.LspUnavailableException
46import completions.lsp.client.LspClient
57import completions.lsp.client.ReconnectingLspClient
68import completions.lsp.components.LspProject
7- import completions.model.Project
8- import completions.model.ProjectFile
99import kotlinx.coroutines.CompletableDeferred
1010import kotlinx.coroutines.CoroutineName
1111import kotlinx.coroutines.CoroutineScope
@@ -21,14 +21,15 @@ import java.io.IOException
2121import java.net.URI
2222import java.nio.file.Path
2323import java.util.concurrent.ConcurrentHashMap
24+ import java.util.concurrent.CopyOnWriteArrayList
2425import java.util.concurrent.atomic.AtomicBoolean
2526import kotlin.time.Duration.Companion.seconds
2627
2728@Component
2829class KotlinLspProxy {
2930
3031 internal lateinit var lspClient: LspClient
31- internal val lspProjects = ConcurrentHashMap < Project , LspProject >()
32+ internal val lspProjects = CopyOnWriteArrayList < LspProject >()
3233
3334 private val available = AtomicBoolean (false )
3435 private val isInitializing = AtomicBoolean (false )
@@ -51,24 +52,24 @@ class KotlinLspProxy {
5152 * This modality is aimed for **stateless** scenarios where we don't care about
5253 * the identity of the client and the project.
5354 *
54- * @param project the project containing the file
55+ * @param request the completion request
5556 * @param line the line number
5657 * @param ch the character position
5758 * @param projectFile the file to be used for completion, defaults to the first file in the project
5859 * @return a list of [CompletionItem]s
5960 */
6061 suspend fun getOneTimeCompletions (
61- project : Project ,
62+ request : CompletionRequest ,
6263 line : Int ,
6364 ch : Int ,
64- projectFile : ProjectFile = project .files.first(),
65+ projectFile : ProjectFile = request .files.first(),
6566 ): List <CompletionItem > {
6667 ensureLspClientReady() ? : return emptyList()
67- val lspProject = lspProjects.getOrPut(project) { createNewProject(project) }
68+ val lspProject = createNewProject(request). also (lspProjects::add)
6869 val uri = lspProject.getDocumentUri(projectFile.name) ? : return emptyList()
6970 lspClient.openDocument(uri, projectFile.text, 1 )
7071 return getCompletions(lspProject, line, ch, projectFile.name)
71- .also { closeProject(project ) }
72+ .also { closeProject(lspProject ) }
7273 }
7374
7475 /* *
@@ -149,17 +150,16 @@ class KotlinLspProxy {
149150 else logger.debug(" Lsp client not ready: ${it.message} " )
150151 }.isSuccess.takeIf { it }
151152
152- private fun createNewProject (project : Project ): LspProject = LspProject .fromProject (project)
153+ private fun createNewProject (project : CompletionRequest ): LspProject = LspProject .fromCompletionRequest (project)
153154
154- internal fun closeProject (project : Project ) {
155- val lspProject = lspProjects[project] ? : return
155+ internal fun closeProject (lspProject : LspProject ) {
156156 lspProject.getDocumentsUris().forEach { uri -> lspClient.closeDocument(uri) }
157157 lspProject.tearDown()
158- lspProjects.remove(project )
158+ lspProjects.remove(lspProject )
159159 }
160160
161161 fun closeAllProjects () {
162- lspProjects.keys. forEach { closeProject(it) }
162+ lspProjects.forEach { closeProject(it) }
163163 lspProjects.clear()
164164 }
165165
@@ -184,10 +184,10 @@ class KotlinLspProxy {
184184 isInitializing.set(false )
185185 }
186186 lspClient.addOnReconnectListener {
187- lspProjects.forEach { ( project, lspProject) ->
187+ lspProjects.forEach { project ->
188188 val file = project.files.first()
189- val uri = lspProject .getDocumentUri(file.name) ? : return @forEach
190- lspProject .resetDocumentVersion(file.name)
189+ val uri = project .getDocumentUri(file.name) ? : return @forEach
190+ project .resetDocumentVersion(file.name)
191191 client.openDocument(uri, file.text, 1 )
192192 }
193193 lspClientInitializedDeferred.complete(Unit )
@@ -242,7 +242,7 @@ class KotlinLspProxy {
242242 * Custom WS messages could be designed to handle this.
243243 */
244244object StatefulKotlinLspProxy {
245- private val clientsProjects = ConcurrentHashMap <String , Project >()
245+ private val clientsProjects = ConcurrentHashMap <String , LspProject >()
246246
247247 /* *
248248 * Retrieve completions for a given line and character position in a project file.
@@ -254,21 +254,22 @@ object StatefulKotlinLspProxy {
254254 * the document**.
255255 *
256256 * @param clientId the user identifier (or session identifier)
257- * @param newProject the project containing the file
257+ * @param completionRequest the request containing the files
258258 * @param line the line number
259259 * @param ch the character position
260260 * @return a list of [CompletionItem]s
261261 */
262262 suspend fun KotlinLspProxy.getCompletionsForClient (
263263 clientId : String ,
264- newProject : Project ,
264+ completionRequest : CompletionRequest ,
265265 line : Int ,
266266 ch : Int ,
267- projectFile : ProjectFile = newProject .files.first(),
267+ projectFile : ProjectFile = completionRequest .files.first(),
268268 ): List <CompletionItem > {
269- val project =
270- clientsProjects[clientId]?.let { ensureDocumentPresent(it, projectFile, clientId) } ? : return emptyList()
271- val lspProject = lspProjects[project] ? : return emptyList()
269+ val lspProject = clientsProjects[clientId]?.let {
270+ ensureDocumentPresent(it, projectFile, clientId)
271+ } ? : return emptyList()
272+
272273 val newContent = projectFile.text
273274 val documentToChange = projectFile.name
274275 changeDocumentContent(lspProject, documentToChange, newContent)
@@ -282,9 +283,10 @@ object StatefulKotlinLspProxy {
282283 * @param clientId the unique identifier for the client that has connected
283284 */
284285 fun KotlinLspProxy.onClientConnected (clientId : String ) {
285- val project = Project ()
286- .also { clientsProjects[clientId] = it }
287- val lspProject = LspProject .fromProject(project, ownerId = clientId).also { lspProjects[project] = it }
286+ val lspProject = LspProject .empty(clientId).also {
287+ lspProjects.add(it)
288+ clientsProjects[clientId] = it
289+ }
288290 lspProject.getDocumentsUris().forEach { uri -> lspClient.openDocument(uri, " " , 1 ) }
289291 }
290292
@@ -298,6 +300,7 @@ object StatefulKotlinLspProxy {
298300 clientsProjects[clientId]?.let {
299301 closeProject(it)
300302 clientsProjects.remove(clientId)
303+ lspProjects.remove(it)
301304 }
302305 }
303306
@@ -309,17 +312,21 @@ object StatefulKotlinLspProxy {
309312 * @param projectFile the file to be opened if not present in the project
310313 * @return the modified project, with the file added if necessary
311314 */
312- private fun KotlinLspProxy.ensureDocumentPresent (project : Project , projectFile : ProjectFile , clientId : String ): Project {
313- if (project.files.contains(projectFile)) return project
315+ private fun KotlinLspProxy.ensureDocumentPresent (
316+ project : LspProject ,
317+ projectFile : ProjectFile ,
318+ clientId : String
319+ ): LspProject {
320+ if (project.containsFile(projectFile)) return project
314321 lspProjects.remove(project)
315- val newProject = project.copy(files = project.files + projectFile)
316- val newLspProject = LspProject .fromProject(newProject, ownerId = clientId )
317- lspProjects[newProject ] = newLspProject
318- clientsProjects[clientId] = newProject
322+ val newLspProject = project.copy(files = project.files + projectFile). also {
323+ lspProjects.add(it )
324+ clientsProjects[clientId ] = it
325+ }
319326 newLspProject.getDocumentUri(projectFile.name)?.let { uri ->
320327 lspClient.openDocument(uri, projectFile.text, 1 )
321328 }
322- return newProject
329+ return newLspProject
323330 }
324331
325332 private fun KotlinLspProxy.changeDocumentContent (
0 commit comments