Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5e2121b
Extend function search for workspace
dulajdilshan Feb 5, 2026
c2db8b2
Add isPublic flag for functions
dulajdilshan Feb 5, 2026
721f702
Merge remote-tracking branch 'upstream/main' into ls-api-workspace
dulajdilshan Feb 11, 2026
fc0b988
Change the order of the workspace function search
dulajdilshan Feb 12, 2026
7c7f129
Implement types search for workspace-level public types
dulajdilshan Feb 12, 2026
b545236
Fix type search tests
dulajdilshan Feb 12, 2026
82c7096
Fix function search tests
dulajdilshan Feb 12, 2026
bde4cbc
Add `public` qualifier support for types
dulajdilshan Feb 12, 2026
e4b0c9f
Fix and add tests
dulajdilshan Feb 12, 2026
61cc617
Fix tests for `isPublic` property
dulajdilshan Feb 12, 2026
fe5e1f9
Fix casing for error value in currency_converter1.json
dulajdilshan Feb 12, 2026
cadd757
Add tests for workspace-level function search
dulajdilshan Feb 16, 2026
eb0f5f1
Update type and function search with "(current)" indicator
dulajdilshan Feb 16, 2026
81e2b2b
Fix tests
dulajdilshan Feb 17, 2026
1c33a32
Add search test for workspace-level types
dulajdilshan Feb 17, 2026
2acf91c
Merge remote-tracking branch 'upstream/main' into ls-api-workspace
dulajdilshan Feb 17, 2026
a7edd2f
Refactor type search logic to use pattern matching and enhance enum g…
dulajdilshan Feb 17, 2026
4727700
Merge remote-tracking branch 'upstream/main' into ls-api-workspace
dulajdilshan Feb 17, 2026
2e65a45
Change keywords of current workspace and integration and update tests
dulajdilshan Feb 17, 2026
71ebd56
Add isLibrary attribute and checker for library projects
dulajdilshan Feb 17, 2026
49e34e7
Revert "Fix casing for error value in currency_converter1.json"
dulajdilshan Feb 17, 2026
fb57607
Change isPublic property description
dulajdilshan Feb 17, 2026
389468a
Add function source code generation tests
dulajdilshan Feb 17, 2026
47c1189
Implement isPublic for data-mapper
dulajdilshan Feb 17, 2026
a6d8cd8
Fix tests
dulajdilshan Feb 17, 2026
76deb63
Update data-mapper test
dulajdilshan Feb 17, 2026
e08a0a3
Merge branch 'main' into ls-api-workspace
dulajdilshan Feb 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ public enum Name {
DATA("Data", "Data nodes are used to create, read, update, delete, and transform data", null),
CURRENT_INTEGRATION("Current Integration", "Functions defined within the current integration",
List.of("Project", "Local", "Function")),
CURRENT_WORKSPACE("Current Workspace", "Functions available in the current workspace",
List.of("Workspace", "Local", "Function")),
AGENT_TOOLS("Agent Tools", "Functions used as agent tools", List.of("Project", "Local", "Function")),
CURRENT_ORGANIZATION("Current Organization", "Components in the current organization",
List.of("Organization", "Function", "Library")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1087,7 +1087,7 @@ public FormBuilder<T> isPublic(boolean value, boolean optional, boolean editable
.editable(editable)
.optional(optional)
.advanced(advanced)
.value(String.valueOf(value))
.value(value)
.type()
.fieldType(Property.ValueType.FLAG)
.selected(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ public static void setMandatoryProperties(NodeBuilder nodeBuilder, String return
.functionDescription(description)
.returnType(returnType, null, true)
.returnDescription(returnDescription)
.isPublic(false, false, true, false)
.nestedProperty();
}

Expand Down Expand Up @@ -164,6 +165,12 @@ public Map<Path, List<TextEdit>> toSource(SourceBuilder sourceBuilder) {
if (annotationsProperty.isPresent()) {
sourceBuilder.token().name(annotationsProperty.get().toSourceCode());
}

Optional<Property> visibilityProperty = sourceBuilder.getProperty(Property.IS_PUBLIC_KEY);
if (visibilityProperty.isPresent() && Boolean.parseBoolean(visibilityProperty.get().value().toString())) {
sourceBuilder.token().keyword(SyntaxKind.PUBLIC_KEYWORD);
}

Optional<Property> isolatedProperty = sourceBuilder.getProperty(Property.IS_ISOLATED_KEY);
if (isolatedProperty.isPresent()) {
sourceBuilder.token().keyword(SyntaxKind.ISOLATED_KEYWORD);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import io.ballerina.compiler.api.symbols.FunctionSymbol;
import io.ballerina.compiler.api.symbols.ModuleSymbol;
import io.ballerina.compiler.api.symbols.Qualifier;
import io.ballerina.compiler.api.symbols.Symbol;
import io.ballerina.compiler.api.symbols.SymbolKind;
import io.ballerina.flowmodelgenerator.core.model.AvailableNode;
import io.ballerina.flowmodelgenerator.core.model.Category;
Expand All @@ -42,7 +41,10 @@
import io.ballerina.modelgenerator.commons.SearchResult;
import io.ballerina.projects.Document;
import io.ballerina.projects.Package;
import io.ballerina.projects.PackageName;
import io.ballerina.projects.Project;
import io.ballerina.projects.directory.BuildProject;
import io.ballerina.projects.directory.WorkspaceProject;
import io.ballerina.tools.diagnostics.Location;
import io.ballerina.tools.text.LineRange;
import org.ballerinalang.langserver.common.utils.PositionUtil;
Expand Down Expand Up @@ -84,6 +86,7 @@ class FunctionSearchCommand extends SearchCommand {
"io", List.of("print", "println", "fileWriteString", "fileWriteJson", "fileReadString", "fileReadJson")
);
private static final String FETCH_KEY = "functions";
public static final String CURRENT_INTEGRATION_INDICATOR = " (current)";
private final List<String> moduleNames;
private final Document functionsDoc;

Expand All @@ -107,7 +110,7 @@ public FunctionSearchCommand(Project project, LineRange position, Map<String, St

@Override
protected List<Item> defaultView() {
buildProjectNodes();
buildWorkspaceNodes();
List<SearchResult> searchResults = new ArrayList<>();
if (!moduleNames.isEmpty()) {
searchResults.addAll(dbManager.searchFunctionsByPackages(moduleNames, List.of(), limit, offset));
Expand All @@ -120,7 +123,7 @@ protected List<Item> defaultView() {

@Override
protected List<Item> search() {
buildProjectNodes();
buildWorkspaceNodes();
List<SearchResult> functionSearchList = dbManager.searchFunctions(query, limit, offset);
buildLibraryNodes(functionSearchList);
return rootBuilder.build().items();
Expand Down Expand Up @@ -180,76 +183,105 @@ protected Map<String, List<SearchResult>> fetchPopularItems() {
return Map.of(FETCH_KEY, dbManager.searchFunctionsByPackages(packageNames, functionNames, limit, offset));
}

private void buildProjectNodes() {
private List<FunctionSymbol> getFunctions(Project project) {
Package currentPackage = project.currentPackage();
List<Symbol> functionSymbols = PackageUtil.getCompilation(currentPackage)

return PackageUtil.getCompilation(currentPackage)
.getSemanticModel(currentPackage.getDefaultModule().moduleId())
.moduleSymbols().stream()
.filter(symbol -> symbol.kind().equals(SymbolKind.FUNCTION) &&
!symbol.nameEquals(AutomationBuilder.MAIN_FUNCTION_NAME))
.map(symbol -> (FunctionSymbol) symbol)
.toList();
Category.Builder projectBuilder = rootBuilder.stepIn(Category.Name.CURRENT_INTEGRATION);
}

private void buildWorkspaceNodes() {
Category.Builder agentToolsBuilder = rootBuilder.stepIn(Category.Name.AGENT_TOOLS);

Optional<WorkspaceProject> workspaceProject = project.workspaceProject();
if (workspaceProject.isEmpty()) {
Category.Builder projectBuilder = rootBuilder.stepIn(Category.Name.CURRENT_INTEGRATION);
buildProjectNodes(project, projectBuilder, agentToolsBuilder);
return;
}

PackageName currProjPackageName = this.project.currentPackage().packageName();

Category.Builder workspaceBuilder = rootBuilder.stepIn(Category.Name.CURRENT_WORKSPACE);

// Build current integration first to ensure it appears at the top
Category.Builder currIntProjBuilder = workspaceBuilder.stepIn(
currProjPackageName.value() + CURRENT_INTEGRATION_INDICATOR, "", List.of());
Category.Builder currIntAgtToolsBuilder = agentToolsBuilder.stepIn(
currProjPackageName.value() + CURRENT_INTEGRATION_INDICATOR, "", List.of());
buildProjectNodes(this.project, currIntProjBuilder, currIntAgtToolsBuilder);

List<BuildProject> projects = workspaceProject.get().projects();
for (BuildProject project : projects) {
PackageName packageName = project.currentPackage().packageName();
if (packageName.equals(currProjPackageName)) {
continue;
}

Category.Builder projectBuilder = workspaceBuilder.stepIn(packageName.value(), "", List.of());
Category.Builder projectAgentToolsBuilder = agentToolsBuilder.stepIn(packageName.value(), "", List.of());
buildProjectNodes(project, projectBuilder, projectAgentToolsBuilder);
}
}

private void buildProjectNodes(Project project,
Category.Builder projectBuilder,
Category.Builder projectAgentToolsBuilder) {
List<FunctionSymbol> functions = getFunctions(project);

boolean isCurrIntProject = this.project.currentPackage().packageName()
.equals(project.currentPackage().packageName());

List<FunctionSymbol> filteredFunctions;
if (!isCurrIntProject) {
filteredFunctions = functions.stream()
.filter(func -> func.qualifiers().contains(Qualifier.PUBLIC))
.toList();
} else {
filteredFunctions = functions;
}

List<Item> availableNodes = new ArrayList<>();
List<Item> availableTools = new ArrayList<>();
for (Symbol symbol : functionSymbols) {
FunctionSymbol functionSymbol = (FunctionSymbol) symbol;
if (functionsDoc != null
&& CommonUtils.isNaturalExpressionBodiedFunction(functionsDoc.syntaxTree(), functionSymbol)) {
// Skip NP functions

for (FunctionSymbol func : filteredFunctions) {
if (isNaturalExprBodiedFunction(func)) {
continue;
}

boolean isDataMappedFunction = false;
Optional<Location> location = symbol.getLocation();
if (location.isPresent()) {
isDataMappedFunction = location.get().lineRange().fileName().equals(DATA_MAPPER_FILE_NAME);
LineRange fnLineRange = location.get().lineRange();
boolean isDataMappedFunction = isDataMappedFunction(func);
if (isDataMappedFunction && isCurrIntProject) {
LineRange fnLineRange = func.getLocation().get().lineRange();
if (fnLineRange.fileName().equals(position.fileName()) &&
PositionUtil.isWithinLineRange(fnLineRange, position)) {
continue;
}
}

if (symbol.getName().isEmpty() ||
(!query.isEmpty() && !symbol.getName().get().toLowerCase(Locale.ROOT)
.contains(query.toLowerCase(Locale.ROOT)))) {
if (!isValidFunctionForSearchQuery(func)) {
continue;
}

boolean isAgentTool = isAgentTool(functionSymbol);
boolean isIsolatedFunction = functionSymbol.qualifiers().contains(Qualifier.ISOLATED);
Metadata metadata = new Metadata.Builder<>(null)
.label(symbol.getName().get())
.description(functionSymbol.documentation()
.flatMap(Documentation::description)
.orElse(null))
.addData("isDataMappedFunction", isDataMappedFunction)
.addData("isAgentTool", isAgentTool)
.addData("isIsolatedFunction", isIsolatedFunction)
.build();
boolean isAgentTool = isAgentTool(func);
boolean isIsolatedFunction = func.qualifiers().contains(Qualifier.ISOLATED);

Codedata.Builder<Object> codedataBuilder = new Codedata.Builder<>(null)
.node(NodeKind.FUNCTION_CALL)
.symbol(symbol.getName().get());
Optional<ModuleSymbol> moduleSymbol = functionSymbol.getModule();
if (moduleSymbol.isPresent()) {
ModuleID id = moduleSymbol.get().id();
codedataBuilder
.org(id.orgName())
.module(id.packageName())
.version(id.version());
}
AvailableNode availableNode = createAvailableNode(func, isDataMappedFunction, isAgentTool,
isIsolatedFunction);

if (isAgentTool) {
availableTools.add(new AvailableNode(metadata, codedataBuilder.build(), true));
availableTools.add(availableNode);
} else {
availableNodes.add(new AvailableNode(metadata, codedataBuilder.build(), true));
availableNodes.add(availableNode);
}
}

projectBuilder.items(availableNodes);
agentToolsBuilder.items(availableTools);
projectAgentToolsBuilder.items(availableTools);
}

private void buildLibraryNodes(List<SearchResult> functionSearchList) {
Expand Down Expand Up @@ -310,4 +342,51 @@ private boolean isAgentTool(FunctionSymbol functionSymbol) {
}
return false;
}

private boolean isNaturalExprBodiedFunction(FunctionSymbol functionSymbol) {
return functionsDoc != null
&& CommonUtils.isNaturalExpressionBodiedFunction(functionsDoc.syntaxTree(), functionSymbol);
}

private boolean isValidFunctionForSearchQuery(FunctionSymbol functionSymbol) {
if (functionSymbol.getName().isEmpty()) {
return false;
}
String functionName = functionSymbol.getName().get().toLowerCase(Locale.ROOT);
return query.isEmpty() || functionName.contains(query.toLowerCase(Locale.ROOT));
}

private boolean isDataMappedFunction(FunctionSymbol functionSymbol) {
Optional<Location> location = functionSymbol.getLocation();
return location.isPresent() && location.get().lineRange().fileName().equals(DATA_MAPPER_FILE_NAME);
}

private AvailableNode createAvailableNode(FunctionSymbol functionSymbol,
boolean isDataMappedFunction,
boolean isAgentTool,
boolean isIsolatedFunction) {
Metadata metadata = new Metadata.Builder<>(null)
.label(functionSymbol.getName().get())
.description(functionSymbol.documentation()
.flatMap(Documentation::description)
.orElse(null))
.addData("isDataMappedFunction", isDataMappedFunction)
.addData("isAgentTool", isAgentTool)
.addData("isIsolatedFunction", isIsolatedFunction)
.build();

Codedata.Builder<Object> codedataBuilder = new Codedata.Builder<>(null)
.node(NodeKind.FUNCTION_CALL)
.symbol(functionSymbol.getName().get());
Optional<ModuleSymbol> moduleSymbol = functionSymbol.getModule();
if (moduleSymbol.isPresent()) {
ModuleID id = moduleSymbol.get().id();
codedataBuilder
.org(id.orgName())
.module(id.packageName())
.version(id.version());
}

return new AvailableNode(metadata, codedataBuilder.build(), true);
}
}
Loading
Loading