Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
@@ -0,0 +1,77 @@
package org.jetbrains.plugins.hocon
package ref

import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.util.TextRange
import com.intellij.psi.*
import com.intellij.util.ProcessingContext
import org.jetbrains.jps.model.java.JavaResourceRootType
import org.jetbrains.plugins.hocon.psi.HStringValue
import org.jetbrains.plugins.hocon.settings.HoconProjectSettings

class HoconFilePathReferenceProvider extends PsiReferenceProvider {
def getReferencesByElement(element: PsiElement, context: ProcessingContext): Array[PsiReference] =
element match {
case hstr: HStringValue =>
val project = element.getProject
val settings = HoconProjectSettings.getInstance(project)
val extensions = settings.fileNavigationExtensionsList
if (extensions.isEmpty) PsiReference.EMPTY_ARRAY
else {
val filePath = hstr.stringValue
if (!extensions.exists(ext => filePath.endsWith("." + ext))) PsiReference.EMPTY_ARRAY
else {
val range = ElementManipulators.getValueTextRange(element)
Array(new HoconFilePathReference(filePath, element, range, settings))
}
}
case _ => PsiReference.EMPTY_ARRAY
}
}

class HoconFilePathReference(
filePath: String,
element: PsiElement,
range: TextRange,
settings: HoconProjectSettings,
) extends PsiReferenceBase[PsiElement](element, range) {

def resolve(): PsiElement = {
val project = element.getProject
val searchRoots = settings.fileNavigationSearchRootsList
val psiManager = PsiManager.getInstance(project)
val modules = ModuleManager.getInstance(project).getModules

val result = modules.iterator.flatMap { module =>
val mrm = ModuleRootManager.getInstance(module)

val baseDirs = if (searchRoots.nonEmpty) {
// Use explicitly configured roots (relative paths from content roots)
mrm.getContentRoots.iterator.flatMap { contentRoot =>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use for comprehension

searchRoots.iterator.flatMap { searchRoot =>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.iterator is useless

val dir = if (searchRoot.isEmpty) contentRoot else contentRoot.findFileByRelativePath(searchRoot)
Option(dir).iterator
Comment on lines +53 to +54

Copilot AI Mar 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the configured-roots branch, searchRoot.isEmpty is treated specially (falling back to contentRoot), but fileNavigationSearchRootsList currently removes empty roots. This makes the searchRoot.isEmpty branch unreachable and can be removed/simplified (or adjust the settings parsing to preserve empty entries if that behavior is intended).

Suggested change
val dir = if (searchRoot.isEmpty) contentRoot else contentRoot.findFileByRelativePath(searchRoot)
Option(dir).iterator
Option(contentRoot.findFileByRelativePath(searchRoot)).iterator

Copilot uses AI. Check for mistakes.
}
}
} else {
// Default: use resource root resolved from the project model (Compile / resourceDirectory)
mrm.getContentEntries.iterator.flatMap { entry =>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use for comprehension

entry.getSourceFolders.iterator
.filter(_.getRootType == JavaResourceRootType.RESOURCE)
.flatMap(sf => Option(sf.getFile).iterator)
}
}

baseDirs

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use for comprehension

.flatMap(dir => Option(dir.findFileByRelativePath(filePath)))
.flatMap(vf => Option(psiManager.findFile(vf)))
}

result.nextOption().orNull
}

override def getVariants: Array[AnyRef] = Array.empty

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be val


override def isSoft: Boolean = true
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ class HoconReferenceContributor extends PsiReferenceContributor {
def registerReferenceProviders(registrar: PsiReferenceRegistrar): Unit = {
registrar.registerReferenceProvider(pattern[HIncludeTarget], new IncludedFileReferenceProvider)
registrar.registerReferenceProvider(pattern[HStringValue], new HoconPropertiesReferenceProvider)
registrar.registerReferenceProvider(pattern[HStringValue], new HoconFilePathReferenceProvider)
}
Comment on lines 14 to 18

Copilot AI Mar 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No tests are added for the new file-path reference resolution behavior. There are existing reference-resolution tests in test/org/jetbrains/plugins/hocon/ref, so this would benefit from a test that (1) sets fileNavigationExtensions/fileNavigationSearchRoots, (2) creates a target file under the configured root, and (3) asserts findReferenceAt(...).resolve() returns the expected PsiFile.

Copilot uses AI. Check for mistakes.
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ class HoconProjectSettings extends PersistentStateComponent[HoconProjectSettings
@BeanProperty var classReferencesOnQuotedStrings = true
@BeanProperty var propertyReferencesOnStrings = true
@BeanProperty var searchInGotoSymbol = false
@BeanProperty var fileNavigationExtensions: String = ""
@BeanProperty var fileNavigationSearchRoots: String = ""

def fileNavigationExtensionsList: List[String] =
if (fileNavigationExtensions.trim.isEmpty) Nil
else fileNavigationExtensions.split(",").map(_.trim).filter(_.nonEmpty).toList

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplication of logic here and in fileNavigationSearchRootsList


Comment on lines +33 to +36

Copilot AI Mar 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fileNavigationExtensionsList currently returns trimmed tokens as-is, so common inputs like ".sql" (with a leading dot) or mixed-case extensions won't match the resolver logic that checks for endsWith("." + ext). Consider normalizing extensions here (e.g., strip a leading '.', lowercase) so the configured value behaves predictably regardless of whether users type sql or .sql.

Copilot uses AI. Check for mistakes.
def fileNavigationSearchRootsList: List[String] =
if (fileNavigationSearchRoots.trim.isEmpty) Nil
else fileNavigationSearchRoots.split(",").map(_.trim).filter(_.nonEmpty).toList

Copilot AI Mar 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fileNavigationSearchRootsList filters out empty entries (filter(_.nonEmpty)), but the resolver later has logic for searchRoot.isEmpty to mean "use content root". As written, that branch can never be hit. Either keep empty entries (and define what they mean), or remove the searchRoot.isEmpty handling in the resolver to avoid dead/contradictory logic.

Suggested change
else fileNavigationSearchRoots.split(",").map(_.trim).filter(_.nonEmpty).toList
else fileNavigationSearchRoots.split(",").map(_.trim).toList

Copilot uses AI. Check for mistakes.
}

object HoconProjectSettings {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,69 @@ public class HoconProjectSettingsPanel {
private final Project project;

private JPanel mainPanel;
private JPanel wrapperPanel;
private JCheckBox classReferencesUnquotedCheckBox;
private JCheckBox classReferencesQuotedCheckBox;
private JCheckBox propertyReferencesCheckBox;
private JCheckBox searchInGotoSymbol;
private JTextField fileNavigationExtensionsField;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use JBList - you won't have to parse the strings and it will look better

private JTextField fileNavigationSearchRootsField;

public HoconProjectSettingsPanel(Project project) {
this.project = project;
buildWrapperPanel();
loadSettings();
}

private void buildWrapperPanel() {
fileNavigationExtensionsField = new JTextField();
fileNavigationSearchRootsField = new JTextField();

JPanel fileNavPanel = new JPanel(new GridBagLayout());
fileNavPanel.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createEtchedBorder(), "File Path Navigation"));

GridBagConstraints gbc = new GridBagConstraints();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use FormBuilder instead of manual Grid

gbc.anchor = GridBagConstraints.WEST;
gbc.insets = new Insets(2, 4, 2, 4);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just use JBUI.insets


gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 0; gbc.fill = GridBagConstraints.NONE;
fileNavPanel.add(new JLabel("File extensions to navigate (comma-separated, e.g. sql,xml):"), gbc);
gbc.gridx = 1; gbc.weightx = 1; gbc.fill = GridBagConstraints.HORIZONTAL;
fileNavPanel.add(fileNavigationExtensionsField, gbc);

gbc.gridx = 0; gbc.gridy = 1; gbc.weightx = 0; gbc.fill = GridBagConstraints.NONE;
fileNavPanel.add(new JLabel("Search root directories (comma-separated, default: resource directory from project model):"), gbc);
gbc.gridx = 1; gbc.weightx = 1; gbc.fill = GridBagConstraints.HORIZONTAL;
fileNavPanel.add(fileNavigationSearchRootsField, gbc);

wrapperPanel = new JPanel(new BorderLayout());
wrapperPanel.add(mainPanel, BorderLayout.NORTH);
wrapperPanel.add(fileNavPanel, BorderLayout.CENTER);
}

public void loadSettings() {
HoconProjectSettings settings = HoconProjectSettings.getInstance(project);
classReferencesUnquotedCheckBox.setSelected(settings.getClassReferencesOnUnquotedStrings());
classReferencesQuotedCheckBox.setSelected(settings.getClassReferencesOnQuotedStrings());
propertyReferencesCheckBox.setSelected(settings.getPropertyReferencesOnStrings());
searchInGotoSymbol.setSelected(settings.getSearchInGotoSymbol());
fileNavigationExtensionsField.setText(settings.getFileNavigationExtensions());
fileNavigationSearchRootsField.setText(settings.getFileNavigationSearchRoots());
}

public JComponent getMainComponent() {
return mainPanel;
return wrapperPanel;
}

public boolean isModified() {
HoconProjectSettings settings = HoconProjectSettings.getInstance(project);
return classReferencesUnquotedCheckBox.isSelected() != settings.getClassReferencesOnUnquotedStrings() ||
classReferencesQuotedCheckBox.isSelected() != settings.getClassReferencesOnQuotedStrings() ||
propertyReferencesCheckBox.isSelected() != settings.getPropertyReferencesOnStrings() ||
searchInGotoSymbol.isSelected() != settings.getSearchInGotoSymbol();
searchInGotoSymbol.isSelected() != settings.getSearchInGotoSymbol() ||
!fileNavigationExtensionsField.getText().equals(settings.getFileNavigationExtensions()) ||
!fileNavigationSearchRootsField.getText().equals(settings.getFileNavigationSearchRoots());
}

public void apply() {
Expand All @@ -48,6 +83,8 @@ public void apply() {
settings.setClassReferencesOnQuotedStrings(classReferencesQuotedCheckBox.isSelected());
settings.setPropertyReferencesOnStrings(propertyReferencesCheckBox.isSelected());
settings.setSearchInGotoSymbol(searchInGotoSymbol.isSelected());
settings.setFileNavigationExtensions(fileNavigationExtensionsField.getText());
settings.setFileNavigationSearchRoots(fileNavigationSearchRootsField.getText());
}

{
Expand Down