Skip to content

[enhancement] Add Custom Base Path Setting for OpenCode Working Directory #11

@diegohb

Description

@diegohb

Issue Title

Add configurable base path setting to override project.basePath for opencode.exe working directory

Problem Description

When Rider opens a Visual Studio solution file (.sln) that's nested in a subdirectory, the plugin uses project.basePath as the working directory for opencode.exe 1 . This results in opencode running from the solution directory rather than the parent code directory, which may not be the desired working directory for file operations and Git commands.

Proposed Solution

Add an optional "Custom Base Path" setting to the connection dialog that allows users to override the default project.basePath. When not specified, the plugin will continue using project.basePath for backward compatibility.

Implementation Details

1. UI Changes

File: src/main/kotlin/ai/opencode/ide/jetbrains/ui/OpenCodeConnectDialog.kt

  • Add a new text field for custom base path in createCenterPanel() 2
  • Add persistence property PROP_CUSTOM_BASE_PATH following the existing pattern 3
  • Update ConnectionInfo data class to include customBasePath field 4

2. Service Layer Changes

File: src/main/kotlin/ai/opencode/ide/jetbrains/OpenCodeService.kt

  • Modify createTerminalUI() to use custom path when available 1
  • Update processConnectionChoice() to pass the custom base path parameter

3. Code Changes Required

// In OpenCodeConnectDialog.kt
private const val PROP_CUSTOM_BASE_PATH = "opencode.customBasePath"

// Add to createCenterPanel()
val basePathLabel = JBLabel("Custom base path (optional):")
val basePathField = JBTextField()
basePathField.toolTipText = "Override project base path for opencode.exe working directory"

// In OpenCodeService.kt
val workingDir = connectionInfo.customBasePath ?: project.basePath
val widget = terminalView.createLocalShellWidget(workingDir, tabName)

Test Requirements

Based on the existing test infrastructure 5 , add the following test cases:

File: src/test/kotlin/ai/opencode/ide/jetbrains/ui/OpenCodeConnectDialogTest.kt

  • Test custom base path field validation
  • Test persistence of custom base path setting
  • Test default behavior when custom base path is empty

File: src/test/kotlin/ai/opencode/ide/jetbrains/OpenCodeServiceTest.kt

  • Test terminal creation with custom base path
  • Test fallback to project.basePath when custom path is null
  • Test path resolution with custom base path

Backward Compatibility

  • Setting defaults to null/empty, preserving current behavior
  • Existing users will see no change in functionality
  • New users can optionally specify custom base path

Acceptance Criteria

  1. ✅ Custom base path field appears in connection dialog
  2. ✅ Setting is persisted across IDE sessions
  3. ✅ Terminal uses custom path when specified
  4. ✅ Falls back to project.basePath when empty
  5. ✅ All existing functionality remains unchanged
  6. ✅ Test coverage for new functionality

Notes

This change leverages the existing settings infrastructure 6 and follows the same pattern as password persistence. The implementation is minimal and focused on solving the Rider solution scenario while maintaining full backward compatibility.

Wiki pages you might want to explore:

Citations

File: src/main/kotlin/ai/opencode/ide/jetbrains/OpenCodeService.kt (L682-684)

        val terminalView = TerminalView.getInstance(project)
        val tabName = "$OPEN_CODE_TAB_PREFIX($port)"
        val widget = terminalView.createLocalShellWidget(project.basePath, tabName)

File: src/main/kotlin/ai/opencode/ide/jetbrains/ui/OpenCodeConnectDialog.kt (L27-32)

    data class ConnectionInfo(
        val hostname: String,
        val port: Int,
        val password: String?,
        val useWebInterface: Boolean
    )

File: src/main/kotlin/ai/opencode/ide/jetbrains/ui/OpenCodeConnectDialog.kt (L53-89)

    override fun createCenterPanel(): JComponent {
        val panel = JPanel(BorderLayout(0, JBUI.scale(8)))
        panel.preferredSize = Dimension(JBUI.scale(300), JBUI.scale(140))

        val formPanel = JPanel(GridLayout(4, 1, 0, JBUI.scale(4)))
        val addressLabel = JBLabel("Server address:")
        val passwordLabel = JBLabel("Server password (optional):")

        // Load saved values
        val props = PropertiesComponent.getInstance()
        // Always suggest a new available port by default
        addressField.text = "127.0.0.1:$defaultPort"
        
        // Load saved password (Base64 encoded for basic obfuscation)
        try {
            val encodedPassword = props.getValue(PROP_LAST_PASSWORD, "")
            if (encodedPassword.isNotBlank()) {
                val decodedPassword = String(Base64.getDecoder().decode(encodedPassword))
                passwordField.text = decodedPassword
            }
        } catch (e: Exception) {
            // Ignore if password retrieval fails
        }

        addressField.toolTipText = "Format: hostname:port (e.g., 127.0.0.1:4096)"
        passwordField.toolTipText = "OPENCODE_SERVER_PASSWORD"
        passwordField.emptyText.text = "For remote OpenCode servers"

        formPanel.add(addressLabel)
        formPanel.add(addressField)
        formPanel.add(passwordLabel)
        formPanel.add(passwordField)

        panel.add(formPanel, BorderLayout.CENTER)

        return panel
    }

File: src/main/kotlin/ai/opencode/ide/jetbrains/ui/OpenCodeConnectDialog.kt (L151-154)

    companion object {
        private const val PROP_LAST_ADDRESS = "opencode.lastAddress"
        private const val PROP_LAST_PASSWORD = "opencode.lastPassword"
        

File: AGENTS.md (L42-49)

- **Run Single Test Class** (Preferred for targeted validation):
  ```bash
  ./gradlew test --tests "ai.opencode.ide.jetbrains.util.PortFinderTest"
  • Run Single Test Method (Precision testing):
    ./gradlew test --tests "ai.opencode.ide.jetbrains.util.PortFinderTest.testPortAvailability"

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions