From 6bf8d904c19951cd373443171a5d16b6ffbb2bec Mon Sep 17 00:00:00 2001 From: Nipuna Fernando Date: Mon, 30 Sep 2024 17:50:12 +0530 Subject: [PATCH 1/2] Maintain one temp directory per project --- .../extension/ExpressionEditorService.java | 7 +- .../extension/ProjectCacheManager.java | 54 +- .../config/{proj.json => proj1.json} | 0 .../resources/completions/config/proj2.json | 640 ++++++++++++++++++ .../resources/completions/config/proj3.json | 621 +++++++++++++++++ 5 files changed, 1304 insertions(+), 18 deletions(-) rename flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/completions/config/{proj.json => proj1.json} (100%) create mode 100644 flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/completions/config/proj2.json create mode 100644 flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/completions/config/proj3.json diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/ExpressionEditorService.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/ExpressionEditorService.java index af85b55ea..e011dd860 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/ExpressionEditorService.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/ExpressionEditorService.java @@ -72,9 +72,10 @@ public CompletableFuture, CompletionList>> completio Path projectPath = this.workspaceManager.projectRoot(filePath); // Create a temporary directory and load the project - ProjectCacheManager projectCacheManager = new ProjectCacheManager(projectPath, filePath); + ProjectCacheManager projectCacheManager = + ProjectCacheManager.InstanceHandler.getInstance(projectPath, filePath); projectCacheManager.createTempDirectory(); - Path destination = projectCacheManager.getDestination(); + Path destination = projectCacheManager.getDestination(filePath); this.workspaceManager.loadProject(destination); // Get the document @@ -90,7 +91,7 @@ public CompletableFuture, CompletionList>> completio TextEdit textEdit = TextEdit.from(TextRange.from(textPosition, 0), statement); TextDocument newTextDocument = textDocument.apply(TextDocumentChange.from(List.of(textEdit).toArray(new TextEdit[0]))); - projectCacheManager.writeContent(newTextDocument); + projectCacheManager.writeContent(newTextDocument, filePath); document.get().modify() .withContent(String.join(System.lineSeparator(), newTextDocument.textLines())) .apply(); diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/ProjectCacheManager.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/ProjectCacheManager.java index c1ce9e71c..7372a97aa 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/ProjectCacheManager.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/ProjectCacheManager.java @@ -26,6 +26,8 @@ import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.util.Comparator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; /** @@ -36,27 +38,27 @@ public class ProjectCacheManager { private final Path sourceDir; - private final Path filePath; private Path destinationProjectPath; - private Path destinationPath; - public ProjectCacheManager(Path sourceDir, Path filePath) { + public ProjectCacheManager(Path sourceDir) { this.sourceDir = sourceDir; - this.filePath = filePath; } public void createTempDirectory() throws IOException { // Create a temporary directory - Path tempDir = Files.createTempDirectory("project-cache"); - Path tempDesintaitonPath = tempDir.resolve(sourceDir.getFileName()); - destinationProjectPath = tempDesintaitonPath; + if (destinationProjectPath == null) { + Path tempDir = Files.createTempDirectory("project-cache"); + destinationProjectPath = tempDir.resolve(sourceDir.getFileName()); + } else { + Files.createDirectories(destinationProjectPath); + } // Copy contents from sourceDir to destinationDir if (Files.isDirectory(sourceDir)) { try (Stream paths = Files.walk(sourceDir)) { paths.forEach(source -> { try { - Files.copy(source, tempDesintaitonPath.resolve(sourceDir.relativize(source)), + Files.copy(source, destinationProjectPath.resolve(sourceDir.relativize(source)), StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { throw new RuntimeException("Failed to copy project directory to cache", e); @@ -65,7 +67,7 @@ public void createTempDirectory() throws IOException { } return; } - Files.copy(sourceDir, tempDesintaitonPath, StandardCopyOption.REPLACE_EXISTING); + Files.copy(sourceDir, destinationProjectPath, StandardCopyOption.REPLACE_EXISTING); } public void deleteCache() throws IOException { @@ -84,21 +86,43 @@ public void deleteCache() throws IOException { Files.delete(destinationProjectPath); } - public void writeContent(TextDocument textDocument) throws IOException { + public void writeContent(TextDocument textDocument, Path filePath) throws IOException { if (destinationProjectPath == null) { throw new RuntimeException("Destination directory is not created"); } - Files.writeString(getDestination(), new String(textDocument.toCharArray()), StandardOpenOption.CREATE, + Files.writeString(getDestination(filePath), new String(textDocument.toCharArray()), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); } - public Path getDestination() { + public Path getDestination(Path filePath) { if (destinationProjectPath == null) { throw new RuntimeException("Destination directory is not created"); } - if (destinationPath == null) { - destinationPath = destinationProjectPath.resolve(sourceDir.relativize(sourceDir.resolve(filePath))); + return destinationProjectPath.resolve(sourceDir.relativize(sourceDir.resolve(filePath))); + } + + /** + * The multiton design pattern to handle `ProjectCacheManager` instances mapped by source directory. Ensures that + * there is only one copy per each project. + * + * @since 1.4.0 + */ + public static class InstanceHandler { + + private static final Map instances = new ConcurrentHashMap<>(); + + private InstanceHandler() { + } + + public static ProjectCacheManager getInstance(Path sourceDir, Path filePath) { + return instances.computeIfAbsent(sourceDir, key -> new ProjectCacheManager(sourceDir)); + } + + public static void removeInstance(Path sourceDir) throws IOException { + ProjectCacheManager manager = instances.remove(sourceDir); + if (manager != null) { + manager.deleteCache(); + } } - return destinationPath; } } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/completions/config/proj.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/completions/config/proj1.json similarity index 100% rename from flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/completions/config/proj.json rename to flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/completions/config/proj1.json diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/completions/config/proj2.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/completions/config/proj2.json new file mode 100644 index 000000000..d51118dbd --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/completions/config/proj2.json @@ -0,0 +1,640 @@ +{ + "description": "", + "filePath": "proj/main.bal", + "expression": "+ ", + "startLine": { + "line": 7, + "offset": 33 + }, + "offset": 2, + "context": { + "triggerKind": "Invoked" + }, + "node": {}, + "completions": [ + { + "label": "start", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "U", + "filterText": "start", + "insertText": "start ", + "insertTextFormat": "Snippet" + }, + { + "label": "wait", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "U", + "filterText": "wait", + "insertText": "wait ", + "insertTextFormat": "Snippet" + }, + { + "label": "flush", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "U", + "filterText": "flush", + "insertText": "flush ", + "insertTextFormat": "Snippet" + }, + { + "label": "from clause", + "kind": "Snippet", + "detail": "Snippet", + "sortText": "T", + "filterText": "from", + "insertText": "from ${1:var} ${2:item} in ${3}", + "insertTextFormat": "Snippet" + }, + { + "label": "io", + "kind": "Module", + "detail": "Module", + "sortText": "S", + "filterText": "io", + "insertText": "io", + "insertTextFormat": "Snippet" + }, + { + "label": "decimal", + "kind": "TypeParameter", + "detail": "Decimal", + "sortText": "R", + "insertText": "decimal", + "insertTextFormat": "Snippet" + }, + { + "label": "error", + "kind": "Event", + "detail": "Error", + "sortText": "P", + "insertText": "error", + "insertTextFormat": "Snippet" + }, + { + "label": "object", + "kind": "Unit", + "detail": "type", + "sortText": "R", + "insertText": "object", + "insertTextFormat": "Snippet" + }, + { + "label": "transaction", + "kind": "Unit", + "detail": "type", + "sortText": "R", + "insertText": "transaction", + "insertTextFormat": "Snippet" + }, + { + "label": "xml", + "kind": "TypeParameter", + "detail": "Xml", + "sortText": "R", + "insertText": "xml", + "insertTextFormat": "Snippet" + }, + { + "label": "table", + "kind": "Unit", + "detail": "type", + "sortText": "R", + "insertText": "table", + "insertTextFormat": "Snippet" + }, + { + "label": "map", + "kind": "Unit", + "detail": "type", + "sortText": "R", + "insertText": "map", + "insertTextFormat": "Snippet" + }, + { + "label": "stream", + "kind": "Unit", + "detail": "type", + "sortText": "R", + "insertText": "stream", + "insertTextFormat": "Snippet" + }, + { + "label": "boolean", + "kind": "TypeParameter", + "detail": "Boolean", + "sortText": "R", + "insertText": "boolean", + "insertTextFormat": "Snippet" + }, + { + "label": "future", + "kind": "TypeParameter", + "detail": "Future", + "sortText": "R", + "insertText": "future", + "insertTextFormat": "Snippet" + }, + { + "label": "int", + "kind": "TypeParameter", + "detail": "Int", + "sortText": "R", + "insertText": "int", + "insertTextFormat": "Snippet" + }, + { + "label": "float", + "kind": "TypeParameter", + "detail": "Float", + "sortText": "R", + "insertText": "float", + "insertTextFormat": "Snippet" + }, + { + "label": "function", + "kind": "TypeParameter", + "detail": "Function", + "sortText": "R", + "insertText": "function", + "insertTextFormat": "Snippet" + }, + { + "label": "string", + "kind": "TypeParameter", + "detail": "String", + "sortText": "R", + "insertText": "string", + "insertTextFormat": "Snippet" + }, + { + "label": "typedesc", + "kind": "TypeParameter", + "detail": "Typedesc", + "sortText": "R", + "insertText": "typedesc", + "insertTextFormat": "Snippet" + }, + { + "label": "service", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "U", + "filterText": "service", + "insertText": "service", + "insertTextFormat": "Snippet" + }, + { + "label": "new", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "U", + "filterText": "new", + "insertText": "new ", + "insertTextFormat": "Snippet" + }, + { + "label": "isolated", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "U", + "filterText": "isolated", + "insertText": "isolated ", + "insertTextFormat": "Snippet" + }, + { + "label": "transactional", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "U", + "filterText": "transactional", + "insertText": "transactional", + "insertTextFormat": "Snippet" + }, + { + "label": "function", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "U", + "filterText": "function", + "insertText": "function ", + "insertTextFormat": "Snippet" + }, + { + "label": "let", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "U", + "filterText": "let", + "insertText": "let", + "insertTextFormat": "Snippet" + }, + { + "label": "typeof", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "U", + "filterText": "typeof", + "insertText": "typeof ", + "insertTextFormat": "Snippet" + }, + { + "label": "trap", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "U", + "filterText": "trap", + "insertText": "trap", + "insertTextFormat": "Snippet" + }, + { + "label": "client", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "U", + "filterText": "client", + "insertText": "client ", + "insertTextFormat": "Snippet" + }, + { + "label": "true", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "U", + "filterText": "true", + "insertText": "true", + "insertTextFormat": "Snippet" + }, + { + "label": "false", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "U", + "filterText": "false", + "insertText": "false", + "insertTextFormat": "Snippet" + }, + { + "label": "null", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "U", + "filterText": "null", + "insertText": "null", + "insertTextFormat": "Snippet" + }, + { + "label": "check", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "U", + "filterText": "check", + "insertText": "check ", + "insertTextFormat": "Snippet" + }, + { + "label": "checkpanic", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "U", + "filterText": "checkpanic", + "insertText": "checkpanic ", + "insertTextFormat": "Snippet" + }, + { + "label": "is", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "U", + "filterText": "is", + "insertText": "is", + "insertTextFormat": "Snippet" + }, + { + "label": "error constructor", + "kind": "Snippet", + "detail": "Snippet", + "sortText": "T", + "filterText": "error", + "insertText": "error(\"${1}\")", + "insertTextFormat": "Snippet" + }, + { + "label": "object constructor", + "kind": "Snippet", + "detail": "Snippet", + "sortText": "T", + "filterText": "object", + "insertText": "object {${1}}", + "insertTextFormat": "Snippet" + }, + { + "label": "base16", + "kind": "Snippet", + "detail": "Snippet", + "sortText": "T", + "filterText": "base16", + "insertText": "base16 `${1}`", + "insertTextFormat": "Snippet" + }, + { + "label": "base64", + "kind": "Snippet", + "detail": "Snippet", + "sortText": "T", + "filterText": "base64", + "insertText": "base64 `${1}`", + "insertTextFormat": "Snippet" + }, + { + "label": "from", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "U", + "filterText": "from", + "insertText": "from ", + "insertTextFormat": "Snippet" + }, + { + "label": "re ``", + "kind": "Snippet", + "detail": "Snippet", + "sortText": "T", + "filterText": "re ``", + "insertText": "re `${1}`", + "insertTextFormat": "Snippet" + }, + { + "label": "string ``", + "kind": "Snippet", + "detail": "Snippet", + "sortText": "T", + "filterText": "string ``", + "insertText": "string `${1}`", + "insertTextFormat": "Snippet" + }, + { + "label": "xml ``", + "kind": "Snippet", + "detail": "Snippet", + "sortText": "T", + "filterText": "xml ``", + "insertText": "xml `${1}`", + "insertTextFormat": "Snippet" + }, + { + "label": "Address", + "kind": "Struct", + "detail": "Record", + "sortText": "Q", + "insertText": "Address", + "insertTextFormat": "Snippet" + }, + { + "label": "Admission", + "kind": "Struct", + "detail": "Record", + "sortText": "Q", + "insertText": "Admission", + "insertTextFormat": "Snippet" + }, + { + "label": "Employee", + "kind": "Struct", + "detail": "Record", + "sortText": "Q", + "insertText": "Employee", + "insertTextFormat": "Snippet" + }, + { + "label": "Location", + "kind": "Struct", + "detail": "Record", + "sortText": "Q", + "insertText": "Location", + "insertTextFormat": "Snippet" + }, + { + "label": "Person", + "kind": "Struct", + "detail": "Record", + "sortText": "Q", + "insertText": "Person", + "insertTextFormat": "Snippet" + }, + { + "label": "StrandData", + "kind": "Struct", + "detail": "Record", + "documentation": { + "left": "Describes Strand execution details for the runtime.\n" + }, + "sortText": "Q", + "insertText": "StrandData", + "insertTextFormat": "Snippet" + }, + { + "label": "Thread", + "kind": "TypeParameter", + "detail": "Union", + "sortText": "R", + "insertText": "Thread", + "insertTextFormat": "Snippet" + }, + { + "label": "add(int a, int b)", + "kind": "Function", + "detail": "int", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _nipunaf/proj:0.1.0_ \n \nAdds two integers and returns the result.\n\n```\nint result = add(5, 3);\n// result will be 8\n``` \n**Params** \n- `int` a: The first integer to be added \n- `int` b: The second integer to be added\n \n \n**Return** `int` \n- The sum of the two integers \n \n# Example \n \n" + } + }, + "sortText": "G", + "filterText": "add", + "insertText": "add(${1})", + "insertTextFormat": "Snippet", + "command": { + "title": "editor.action.triggerParameterHints", + "command": "editor.action.triggerParameterHints" + } + }, + { + "label": "greet(string name)", + "kind": "Function", + "detail": "string", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _nipunaf/proj:0.1.0_ \n \n \n**Params** \n- `string` name \n \n**Return** `string` \n \n" + } + }, + "sortText": "AC", + "filterText": "greet", + "insertText": "greet(${1})", + "insertTextFormat": "Snippet", + "command": { + "title": "editor.action.triggerParameterHints", + "command": "editor.action.triggerParameterHints" + } + }, + { + "label": "main()", + "kind": "Function", + "detail": "()", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _nipunaf/proj:0.1.0_ \n \n \n" + } + }, + "sortText": "ZD", + "filterText": "main", + "insertText": "main()", + "insertTextFormat": "Snippet" + }, + { + "label": "name", + "kind": "Variable", + "detail": "string", + "sortText": "AB", + "insertText": "name", + "insertTextFormat": "Snippet" + }, + { + "label": "prefixSum(int[] numbers)", + "kind": "Function", + "detail": "int[]", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _nipunaf/proj:0.1.0_ \n \nComputes the prefix sum of an array of integers.\n\nThe prefix sum of an array is a new array where each element at index `i` is the sum of the elements\nfrom the start of the array up to index `i`.\n \n**Params** \n- `int[]` numbers: The array of integers for which the prefix sum is to be computed. \n \n**Return** `int[]` \n- An array of integers representing the prefix sum of the input array. \n \n" + } + }, + "sortText": "G", + "filterText": "prefixSum", + "insertText": "prefixSum(${1})", + "insertTextFormat": "Snippet", + "command": { + "title": "editor.action.triggerParameterHints", + "command": "editor.action.triggerParameterHints" + } + }, + { + "label": "safeDivide(float a, float b)", + "kind": "Function", + "detail": "float|error", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _nipunaf/proj:0.1.0_ \n \nPerforms a safe division operation.\n\nThis function divides the given numerator by the denominator and returns the result.\nIf the denominator is zero, it returns an error indicating a division by zero.\n \n**Params** \n- `float` a: The numerator of type `float`. \n- `float` b: The denominator of type `float`.\n\n# Returns\n- `float` - The result of the division if the denominator is not zero.\n- `error` - An error indicating division by zero if the denominator is zero.\n \n \n**Return** `float|error` \n- The result of the division or an error. \n \n" + } + }, + "sortText": "G", + "filterText": "safeDivide", + "insertText": "safeDivide(${1})", + "insertTextFormat": "Snippet", + "command": { + "title": "editor.action.triggerParameterHints", + "command": "editor.action.triggerParameterHints" + } + }, + { + "label": "sum(int... numbers)", + "kind": "Function", + "detail": "int", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _nipunaf/proj:0.1.0_ \n \n \n**Params** \n- `int[]` numbers \n \n**Return** `int` \n \n" + } + }, + "sortText": "G", + "filterText": "sum", + "insertText": "sum(${1})", + "insertTextFormat": "Snippet", + "command": { + "title": "editor.action.triggerParameterHints", + "command": "editor.action.triggerParameterHints" + } + }, + { + "label": "transform(Person person, Admission admission)", + "kind": "Function", + "detail": "Employee", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _nipunaf/proj:0.1.0_ \n \n \n**Params** \n- `Person` person \n- `Admission` admission \n \n**Return** `Employee` \n \n" + } + }, + "sortText": "G", + "filterText": "transform", + "insertText": "transform(${1})", + "insertTextFormat": "Snippet", + "command": { + "title": "editor.action.triggerParameterHints", + "command": "editor.action.triggerParameterHints" + } + }, + { + "label": "readonly", + "kind": "TypeParameter", + "detail": "Readonly", + "sortText": "R", + "insertText": "readonly", + "insertTextFormat": "Snippet" + }, + { + "label": "handle", + "kind": "TypeParameter", + "detail": "Handle", + "sortText": "R", + "insertText": "handle", + "insertTextFormat": "Snippet" + }, + { + "label": "never", + "kind": "TypeParameter", + "detail": "Never", + "sortText": "R", + "insertText": "never", + "insertTextFormat": "Snippet" + }, + { + "label": "json", + "kind": "TypeParameter", + "detail": "Json", + "sortText": "R", + "insertText": "json", + "insertTextFormat": "Snippet" + }, + { + "label": "anydata", + "kind": "TypeParameter", + "detail": "Anydata", + "sortText": "R", + "insertText": "anydata", + "insertTextFormat": "Snippet" + }, + { + "label": "any", + "kind": "TypeParameter", + "detail": "Any", + "sortText": "R", + "insertText": "any", + "insertTextFormat": "Snippet" + }, + { + "label": "byte", + "kind": "TypeParameter", + "detail": "Byte", + "sortText": "R", + "insertText": "byte", + "insertTextFormat": "Snippet" + } + ] +} diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/completions/config/proj3.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/completions/config/proj3.json new file mode 100644 index 000000000..9560e0983 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/completions/config/proj3.json @@ -0,0 +1,621 @@ +{ + "description": "", + "filePath": "proj/fn.bal", + "expression": "", + "startLine": { + "line": 33, + "offset": 20 + }, + "offset": 0, + "context": { + "triggerKind": "Invoked" + }, + "node": {}, + "completions": [ + { + "label": "decimal", + "kind": "TypeParameter", + "detail": "Decimal", + "sortText": "BN", + "insertText": "decimal", + "insertTextFormat": "Snippet" + }, + { + "label": "error", + "kind": "Event", + "detail": "Error", + "sortText": "BL", + "insertText": "error", + "insertTextFormat": "Snippet" + }, + { + "label": "object", + "kind": "Unit", + "detail": "type", + "sortText": "ARR", + "insertText": "object", + "insertTextFormat": "Snippet" + }, + { + "label": "transaction", + "kind": "Unit", + "detail": "type", + "sortText": "ARR", + "insertText": "transaction", + "insertTextFormat": "Snippet" + }, + { + "label": "xml", + "kind": "TypeParameter", + "detail": "Xml", + "sortText": "BN", + "insertText": "xml", + "insertTextFormat": "Snippet" + }, + { + "label": "table", + "kind": "Unit", + "detail": "type", + "sortText": "ARR", + "insertText": "table", + "insertTextFormat": "Snippet" + }, + { + "label": "map", + "kind": "Unit", + "detail": "type", + "sortText": "ARR", + "insertText": "map", + "insertTextFormat": "Snippet" + }, + { + "label": "stream", + "kind": "Unit", + "detail": "type", + "sortText": "ARR", + "insertText": "stream", + "insertTextFormat": "Snippet" + }, + { + "label": "boolean", + "kind": "TypeParameter", + "detail": "Boolean", + "sortText": "BN", + "insertText": "boolean", + "insertTextFormat": "Snippet" + }, + { + "label": "future", + "kind": "TypeParameter", + "detail": "Future", + "sortText": "BN", + "insertText": "future", + "insertTextFormat": "Snippet" + }, + { + "label": "int", + "kind": "TypeParameter", + "detail": "Int", + "sortText": "BN", + "insertText": "int", + "insertTextFormat": "Snippet" + }, + { + "label": "float", + "kind": "TypeParameter", + "detail": "Float", + "sortText": "BN", + "insertText": "float", + "insertTextFormat": "Snippet" + }, + { + "label": "function", + "kind": "TypeParameter", + "detail": "Function", + "sortText": "BN", + "insertText": "function", + "insertTextFormat": "Snippet" + }, + { + "label": "string", + "kind": "TypeParameter", + "detail": "String", + "sortText": "BN", + "insertText": "string", + "insertTextFormat": "Snippet" + }, + { + "label": "typedesc", + "kind": "TypeParameter", + "detail": "Typedesc", + "sortText": "BN", + "insertText": "typedesc", + "insertTextFormat": "Snippet" + }, + { + "label": "service", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "AUQ", + "filterText": "service", + "insertText": "service", + "insertTextFormat": "Snippet" + }, + { + "label": "new", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "AUQ", + "filterText": "new", + "insertText": "new ", + "insertTextFormat": "Snippet" + }, + { + "label": "isolated", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "AUQ", + "filterText": "isolated", + "insertText": "isolated ", + "insertTextFormat": "Snippet" + }, + { + "label": "transactional", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "AUQ", + "filterText": "transactional", + "insertText": "transactional", + "insertTextFormat": "Snippet" + }, + { + "label": "function", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "AUQ", + "filterText": "function", + "insertText": "function ", + "insertTextFormat": "Snippet" + }, + { + "label": "let", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "AUQ", + "filterText": "let", + "insertText": "let", + "insertTextFormat": "Snippet" + }, + { + "label": "typeof", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "AUQ", + "filterText": "typeof", + "insertText": "typeof ", + "insertTextFormat": "Snippet" + }, + { + "label": "trap", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "AUQ", + "filterText": "trap", + "insertText": "trap", + "insertTextFormat": "Snippet" + }, + { + "label": "client", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "AUQ", + "filterText": "client", + "insertText": "client ", + "insertTextFormat": "Snippet" + }, + { + "label": "true", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "AUQ", + "filterText": "true", + "insertText": "true", + "insertTextFormat": "Snippet" + }, + { + "label": "false", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "AUQ", + "filterText": "false", + "insertText": "false", + "insertTextFormat": "Snippet" + }, + { + "label": "null", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "AUQ", + "filterText": "null", + "insertText": "null", + "insertTextFormat": "Snippet" + }, + { + "label": "check", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "AUQ", + "filterText": "check", + "insertText": "check ", + "insertTextFormat": "Snippet" + }, + { + "label": "checkpanic", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "AUQ", + "filterText": "checkpanic", + "insertText": "checkpanic ", + "insertTextFormat": "Snippet" + }, + { + "label": "is", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "AUQ", + "filterText": "is", + "insertText": "is", + "insertTextFormat": "Snippet" + }, + { + "label": "error constructor", + "kind": "Snippet", + "detail": "Snippet", + "sortText": "ATP", + "filterText": "error", + "insertText": "error(\"${1}\")", + "insertTextFormat": "Snippet" + }, + { + "label": "object constructor", + "kind": "Snippet", + "detail": "Snippet", + "sortText": "ATP", + "filterText": "object", + "insertText": "object {${1}}", + "insertTextFormat": "Snippet" + }, + { + "label": "base16", + "kind": "Snippet", + "detail": "Snippet", + "sortText": "ATP", + "filterText": "base16", + "insertText": "base16 `${1}`", + "insertTextFormat": "Snippet" + }, + { + "label": "base64", + "kind": "Snippet", + "detail": "Snippet", + "sortText": "ATP", + "filterText": "base64", + "insertText": "base64 `${1}`", + "insertTextFormat": "Snippet" + }, + { + "label": "from", + "kind": "Keyword", + "detail": "Keyword", + "sortText": "AUQ", + "filterText": "from", + "insertText": "from ", + "insertTextFormat": "Snippet" + }, + { + "label": "re ``", + "kind": "Snippet", + "detail": "Snippet", + "sortText": "ATP", + "filterText": "re ``", + "insertText": "re `${1}`", + "insertTextFormat": "Snippet" + }, + { + "label": "string ``", + "kind": "Snippet", + "detail": "Snippet", + "sortText": "ATP", + "filterText": "string ``", + "insertText": "string `${1}`", + "insertTextFormat": "Snippet" + }, + { + "label": "xml ``", + "kind": "Snippet", + "detail": "Snippet", + "sortText": "ATP", + "filterText": "xml ``", + "insertText": "xml `${1}`", + "insertTextFormat": "Snippet" + }, + { + "label": "Address", + "kind": "Struct", + "detail": "Record", + "sortText": "BM", + "insertText": "Address", + "insertTextFormat": "Snippet" + }, + { + "label": "Admission", + "kind": "Struct", + "detail": "Record", + "sortText": "BM", + "insertText": "Admission", + "insertTextFormat": "Snippet" + }, + { + "label": "Employee", + "kind": "Struct", + "detail": "Record", + "sortText": "BM", + "insertText": "Employee", + "insertTextFormat": "Snippet" + }, + { + "label": "Location", + "kind": "Struct", + "detail": "Record", + "sortText": "BM", + "insertText": "Location", + "insertTextFormat": "Snippet" + }, + { + "label": "Person", + "kind": "Struct", + "detail": "Record", + "sortText": "BM", + "insertText": "Person", + "insertTextFormat": "Snippet" + }, + { + "label": "StrandData", + "kind": "Struct", + "detail": "Record", + "documentation": { + "left": "Describes Strand execution details for the runtime.\n" + }, + "sortText": "BM", + "insertText": "StrandData", + "insertTextFormat": "Snippet" + }, + { + "label": "Thread", + "kind": "TypeParameter", + "detail": "Union", + "sortText": "BN", + "insertText": "Thread", + "insertTextFormat": "Snippet" + }, + { + "label": "add(int a, int b)", + "kind": "Function", + "detail": "int", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _nipunaf/proj:0.1.0_ \n \nAdds two integers and returns the result.\n\n```\nint result = add(5, 3);\n// result will be 8\n``` \n**Params** \n- `int` a: The first integer to be added \n- `int` b: The second integer to be added\n \n \n**Return** `int` \n- The sum of the two integers \n \n# Example \n \n" + } + }, + "sortText": "AACC", + "filterText": "add", + "insertText": "add(${1})", + "insertTextFormat": "Snippet", + "command": { + "title": "editor.action.triggerParameterHints", + "command": "editor.action.triggerParameterHints" + } + }, + { + "label": "greet(string name)", + "kind": "Function", + "detail": "string", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _nipunaf/proj:0.1.0_ \n \n \n**Params** \n- `string` name \n \n**Return** `string` \n \n" + } + }, + "sortText": "AGC", + "filterText": "greet", + "insertText": "greet(${1})", + "insertTextFormat": "Snippet", + "command": { + "title": "editor.action.triggerParameterHints", + "command": "editor.action.triggerParameterHints" + } + }, + { + "label": "main()", + "kind": "Function", + "detail": "()", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _nipunaf/proj:0.1.0_ \n \n \n" + } + }, + "sortText": "AZDZ", + "filterText": "main", + "insertText": "main()", + "insertTextFormat": "Snippet" + }, + { + "label": "numbers", + "kind": "Variable", + "detail": "int[]", + "sortText": "AFB", + "insertText": "numbers", + "insertTextFormat": "Snippet" + }, + { + "label": "prefixSum(int[] numbers)", + "kind": "Function", + "detail": "int[]", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _nipunaf/proj:0.1.0_ \n \nComputes the prefix sum of an array of integers.\n\nThe prefix sum of an array is a new array where each element at index `i` is the sum of the elements\nfrom the start of the array up to index `i`.\n \n**Params** \n- `int[]` numbers: The array of integers for which the prefix sum is to be computed. \n \n**Return** `int[]` \n- An array of integers representing the prefix sum of the input array. \n \n" + } + }, + "sortText": "AGC", + "filterText": "prefixSum", + "insertText": "prefixSum(${1})", + "insertTextFormat": "Snippet", + "command": { + "title": "editor.action.triggerParameterHints", + "command": "editor.action.triggerParameterHints" + } + }, + { + "label": "result", + "kind": "Variable", + "detail": "int[]", + "sortText": "AFB", + "insertText": "result", + "insertTextFormat": "Snippet" + }, + { + "label": "safeDivide(float a, float b)", + "kind": "Function", + "detail": "float|error", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _nipunaf/proj:0.1.0_ \n \nPerforms a safe division operation.\n\nThis function divides the given numerator by the denominator and returns the result.\nIf the denominator is zero, it returns an error indicating a division by zero.\n \n**Params** \n- `float` a: The numerator of type `float`. \n- `float` b: The denominator of type `float`.\n\n# Returns\n- `float` - The result of the division if the denominator is not zero.\n- `error` - An error indicating division by zero if the denominator is zero.\n \n \n**Return** `float|error` \n- The result of the division or an error. \n \n" + } + }, + "sortText": "AGC", + "filterText": "safeDivide", + "insertText": "safeDivide(${1})", + "insertTextFormat": "Snippet", + "command": { + "title": "editor.action.triggerParameterHints", + "command": "editor.action.triggerParameterHints" + } + }, + { + "label": "sum(int... numbers)", + "kind": "Function", + "detail": "int", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _nipunaf/proj:0.1.0_ \n \n \n**Params** \n- `int[]` numbers \n \n**Return** `int` \n \n" + } + }, + "sortText": "AACC", + "filterText": "sum", + "insertText": "sum(${1})", + "insertTextFormat": "Snippet", + "command": { + "title": "editor.action.triggerParameterHints", + "command": "editor.action.triggerParameterHints" + } + }, + { + "label": "transform(Person person, Admission admission)", + "kind": "Function", + "detail": "Employee", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _nipunaf/proj:0.1.0_ \n \n \n**Params** \n- `Person` person \n- `Admission` admission \n \n**Return** `Employee` \n \n" + } + }, + "sortText": "AGC", + "filterText": "transform", + "insertText": "transform(${1})", + "insertTextFormat": "Snippet", + "command": { + "title": "editor.action.triggerParameterHints", + "command": "editor.action.triggerParameterHints" + } + }, + { + "label": "readonly", + "kind": "TypeParameter", + "detail": "Readonly", + "sortText": "BN", + "insertText": "readonly", + "insertTextFormat": "Snippet" + }, + { + "label": "handle", + "kind": "TypeParameter", + "detail": "Handle", + "sortText": "BN", + "insertText": "handle", + "insertTextFormat": "Snippet" + }, + { + "label": "never", + "kind": "TypeParameter", + "detail": "Never", + "sortText": "BN", + "insertText": "never", + "insertTextFormat": "Snippet" + }, + { + "label": "json", + "kind": "TypeParameter", + "detail": "Json", + "sortText": "BN", + "insertText": "json", + "insertTextFormat": "Snippet" + }, + { + "label": "anydata", + "kind": "TypeParameter", + "detail": "Anydata", + "sortText": "BN", + "insertText": "anydata", + "insertTextFormat": "Snippet" + }, + { + "label": "any", + "kind": "TypeParameter", + "detail": "Any", + "sortText": "BN", + "insertText": "any", + "insertTextFormat": "Snippet" + }, + { + "label": "byte", + "kind": "TypeParameter", + "detail": "Byte", + "sortText": "BN", + "insertText": "byte", + "insertTextFormat": "Snippet" + }, + { + "label": "...prefixSum(int[] numbers)", + "kind": "Function", + "detail": "int[]", + "sortText": "AAD", + "filterText": "prefixSum", + "insertText": "...prefixSum(${1})", + "insertTextFormat": "Snippet" + }, + { + "label": "...result", + "kind": "Variable", + "detail": "int[]", + "sortText": "AAC", + "filterText": "result", + "insertText": "...result", + "insertTextFormat": "Snippet" + } + ] +} From 2c3d5dbba9f8aad8221b59d4385f5524866614ff Mon Sep 17 00:00:00 2001 From: Nipuna Fernando Date: Wed, 2 Oct 2024 13:03:29 +0530 Subject: [PATCH 2/2] Send a didChange event to the shadowed project --- .../extension/ExpressionEditorService.java | 25 ++++++-- .../extension/ProjectCacheManager.java | 57 ++++++++++--------- .../src/main/java/module-info.java | 1 + 3 files changed, 51 insertions(+), 32 deletions(-) diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/ExpressionEditorService.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/ExpressionEditorService.java index e011dd860..4bc78211b 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/ExpressionEditorService.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/ExpressionEditorService.java @@ -31,6 +31,9 @@ import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.CompletionList; import org.eclipse.lsp4j.CompletionParams; +import org.eclipse.lsp4j.DidChangeWatchedFilesParams; +import org.eclipse.lsp4j.FileChangeType; +import org.eclipse.lsp4j.FileEvent; import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.jsonrpc.messages.Either; @@ -65,17 +68,23 @@ public Class getRemoteInterface() { public CompletableFuture, CompletionList>> completion( ExpressionEditorCompletionRequest request) { return CompletableFuture.supplyAsync(() -> { + Path projectPath = null; try { - // Load the project + // Load the original project Path filePath = Path.of(request.filePath()); this.workspaceManager.loadProject(filePath); - Path projectPath = this.workspaceManager.projectRoot(filePath); + projectPath = this.workspaceManager.projectRoot(filePath); - // Create a temporary directory and load the project + // Load the shadowed project ProjectCacheManager projectCacheManager = - ProjectCacheManager.InstanceHandler.getInstance(projectPath, filePath); - projectCacheManager.createTempDirectory(); + ProjectCacheManager.InstanceHandler.getInstance(projectPath); + projectCacheManager.copyContent(); Path destination = projectCacheManager.getDestination(filePath); + + FileEvent fileEvent = new FileEvent(destination.toUri().toString(), FileChangeType.Changed); + DidChangeWatchedFilesParams didChangeWatchedFilesParams = + new DidChangeWatchedFilesParams(List.of(fileEvent)); + this.langServer.getWorkspaceService().didChangeWatchedFiles(didChangeWatchedFilesParams); this.workspaceManager.loadProject(destination); // Get the document @@ -106,10 +115,14 @@ public CompletableFuture, CompletionList>> completio CompletableFuture, CompletionList>> completableFuture = langServer.getTextDocumentService().completion(params); Either, CompletionList> completions = completableFuture.join(); - projectCacheManager.deleteCache(); + projectCacheManager.deleteContent(); return completions; } catch (Throwable e) { return Either.forLeft(List.of()); + } finally { + if (projectPath != null) { + ProjectCacheManager.InstanceHandler.release(projectPath); + } } }); } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/ProjectCacheManager.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/ProjectCacheManager.java index 7372a97aa..f970a23d0 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/ProjectCacheManager.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/ProjectCacheManager.java @@ -28,6 +28,8 @@ import java.util.Comparator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Stream; /** @@ -35,30 +37,28 @@ * * @since 1.4.0 */ -public class ProjectCacheManager { +class ProjectCacheManager { private final Path sourceDir; - private Path destinationProjectPath; + private final Path tempDir; public ProjectCacheManager(Path sourceDir) { this.sourceDir = sourceDir; - } - - public void createTempDirectory() throws IOException { - // Create a temporary directory - if (destinationProjectPath == null) { - Path tempDir = Files.createTempDirectory("project-cache"); - destinationProjectPath = tempDir.resolve(sourceDir.getFileName()); - } else { - Files.createDirectories(destinationProjectPath); + try { + Path tempDirPath = Files.createTempDirectory("project-cache"); + this.tempDir = tempDirPath.resolve(sourceDir.getFileName()); + } catch (IOException e) { + throw new RuntimeException(e); } + } + public void copyContent() throws IOException { // Copy contents from sourceDir to destinationDir if (Files.isDirectory(sourceDir)) { try (Stream paths = Files.walk(sourceDir)) { paths.forEach(source -> { try { - Files.copy(source, destinationProjectPath.resolve(sourceDir.relativize(source)), + Files.copy(source, tempDir.resolve(sourceDir.relativize(source)), StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { throw new RuntimeException("Failed to copy project directory to cache", e); @@ -67,12 +67,12 @@ public void createTempDirectory() throws IOException { } return; } - Files.copy(sourceDir, destinationProjectPath, StandardCopyOption.REPLACE_EXISTING); + Files.copy(sourceDir, tempDir, StandardCopyOption.REPLACE_EXISTING); } - public void deleteCache() throws IOException { - if (Files.isDirectory(destinationProjectPath)) { - try (Stream paths = Files.walk(destinationProjectPath)) { + public void deleteContent() throws IOException { + if (Files.isDirectory(tempDir)) { + try (Stream paths = Files.walk(tempDir)) { paths.sorted(Comparator.reverseOrder()).forEach(source -> { try { Files.delete(source); @@ -83,11 +83,11 @@ public void deleteCache() throws IOException { } return; } - Files.delete(destinationProjectPath); + Files.delete(tempDir); } public void writeContent(TextDocument textDocument, Path filePath) throws IOException { - if (destinationProjectPath == null) { + if (tempDir == null) { throw new RuntimeException("Destination directory is not created"); } Files.writeString(getDestination(filePath), new String(textDocument.toCharArray()), StandardOpenOption.CREATE, @@ -95,10 +95,10 @@ public void writeContent(TextDocument textDocument, Path filePath) throws IOExce } public Path getDestination(Path filePath) { - if (destinationProjectPath == null) { + if (tempDir == null) { throw new RuntimeException("Destination directory is not created"); } - return destinationProjectPath.resolve(sourceDir.relativize(sourceDir.resolve(filePath))); + return tempDir.resolve(sourceDir.relativize(sourceDir.resolve(filePath))); } /** @@ -107,21 +107,26 @@ public Path getDestination(Path filePath) { * * @since 1.4.0 */ - public static class InstanceHandler { + + static class InstanceHandler { private static final Map instances = new ConcurrentHashMap<>(); + private static final Map locks = new ConcurrentHashMap<>(); private InstanceHandler() { } - public static ProjectCacheManager getInstance(Path sourceDir, Path filePath) { + public static ProjectCacheManager getInstance(Path sourceDir) { + Lock lock = locks.computeIfAbsent(sourceDir, key -> new ReentrantLock()); + lock.lock(); return instances.computeIfAbsent(sourceDir, key -> new ProjectCacheManager(sourceDir)); } - public static void removeInstance(Path sourceDir) throws IOException { - ProjectCacheManager manager = instances.remove(sourceDir); - if (manager != null) { - manager.deleteCache(); + public static void release(Path sourceDir) { + Lock lock = locks.get(sourceDir); + if (lock != null) { + lock.unlock(); + locks.remove(sourceDir); } } } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/module-info.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/module-info.java index 7001d666e..bc2adb5e7 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/module-info.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/module-info.java @@ -25,4 +25,5 @@ requires io.ballerina.tools.api; requires io.ballerina.flow.model.generator; requires io.ballerina.openapi.core; + requires io.ballerina.language.server.core; }