Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for #262 - REPL does not start on windows #264

Merged
merged 3 commits into from
Jan 24, 2024
Merged
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
Expand Up @@ -10,10 +10,9 @@ abstract class MicroPythonAction : AnAction() {
val project = e.project ?: return
val facet = project.firstMicroPythonFacet
if (facet != null) {
e.presentation.isEnabled = facet.checkValid() == ValidationResult.OK
e.presentation.isEnabledAndVisible = facet.checkValid() == ValidationResult.OK
} else {
e.presentation.isVisible = false
e.presentation.isEnabled = false
e.presentation.isEnabledAndVisible = false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,27 @@

package com.jetbrains.micropython.actions

import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.components.service
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.module.ModuleUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.wm.ToolWindowManager
import com.jetbrains.micropython.repl.MicroPythonReplManager
import com.jetbrains.micropython.settings.firstMicroPythonFacet

class RunMicroReplAction : MicroPythonAction() {

override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT

override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
val editor = FileEditorManagerEx.getInstanceEx(project).selectedTextEditor ?: return

/*
Here we make best effort to find out module which is relevant to the current event.
Here we make our best to find out module which is relevant to the current event.
There are two cases to consider:
1) There is an open file present.
2) No files are opened.
Expand All @@ -48,9 +49,6 @@ class RunMicroReplAction : MicroPythonAction() {
project.firstMicroPythonFacet?.module
}

if (module != null) {
MicroPythonReplManager.getInstance(module).startREPL()
ToolWindowManager.getInstance(project).getToolWindow("MicroPython")?.show()
}
project.service<MicroPythonReplManager>().startOrRestartRepl()
}
}
116 changes: 14 additions & 102 deletions src/main/kotlin/com/jetbrains/micropython/repl/MicroPythonReplManager.kt
Original file line number Diff line number Diff line change
@@ -1,112 +1,24 @@
package com.jetbrains.micropython.repl

import com.intellij.openapi.components.Service
import com.intellij.openapi.module.Module
import com.jediterm.terminal.TtyConnector
import com.jetbrains.micropython.settings.MicroPythonFacet
import com.jetbrains.micropython.settings.microPythonFacet
import com.pty4j.PtyProcess
import org.jetbrains.plugins.terminal.LocalTerminalDirectRunner
import org.jetbrains.plugins.terminal.ShellStartupOptions
import com.intellij.openapi.project.Project
import com.intellij.util.messages.Topic

interface CommsEventListener {
fun onProcessStarted(ttyConnector: TtyConnector)
fun onProcessDestroyed()
fun onProcessCreationFailed(reason: String)
}

@Service
class MicroPythonReplManager(module: Module) {
private val currentModule: Module = module
private var listeners: MutableList<CommsEventListener> = mutableListOf()
private var currentProcess: Process? = null
private var currentConnector: TtyConnector? = null

companion object {
fun getInstance(module: Module): MicroPythonReplManager =
module.getService(MicroPythonReplManager::class.java)
}

fun startREPL() {
if (isRunning) {
stopREPL()
}

val facet = currentModule.microPythonFacet ?: return
val devicePath = facet.getOrDetectDevicePathSynchronously()

if (facet.pythonPath == null) {
notifyProcessCreationFailed("Valid Python interpreter is needed to start a REPL!")
return
}

if (devicePath == null) {
notifyProcessCreationFailed("Device path is not specified, please check settings.")
return
}

val initialShellCommand = mutableListOf(
facet.pythonPath!!,
"${MicroPythonFacet.scriptsPath}/microrepl.py",
devicePath
)

val terminalRunner = object : LocalTerminalDirectRunner(currentModule.project) {
override fun getInitialCommand(envs: MutableMap<String, String>): MutableList<String> {
return initialShellCommand
}

fun getTtyConnector(process: PtyProcess): TtyConnector {
return this.createTtyConnector(process)
}
}

synchronized(this) {
val terminalOptions = ShellStartupOptions.Builder()
.workingDirectory(devicePath)
.shellCommand(initialShellCommand)
.build()
val process = terminalRunner.createProcess(terminalOptions)
val ttyConnector = terminalRunner.getTtyConnector(process)

currentProcess = process
currentConnector = ttyConnector
notifyProcessStarted(ttyConnector)
}
}

fun stopREPL() {
synchronized(this) {
currentProcess?.let {
it.destroy()
notifyProcessDestroyed()
}
currentProcess = null
}
}

val isRunning: Boolean
get() = currentProcess?.isAlive ?: false

fun addListener(listener: CommsEventListener) {
listeners.add(listener)
interface MicroPythonReplControl {
fun stopRepl()
fun startOrRestartRepl()
}

currentConnector?.let { listener.onProcessStarted(it) }
}
@Service(Service.Level.PROJECT)
class MicroPythonReplManager(private val project: Project) : MicroPythonReplControl {
override fun stopRepl() =
project.messageBus.syncPublisher(MICROPYTHON_REPL_CONTROL).stopRepl()

fun removeListener(listener: CommsEventListener) {
listeners.remove(listener)
}

private fun notifyProcessStarted(connector: TtyConnector) {
listeners.forEach { it.onProcessStarted(connector) }
}
override fun startOrRestartRepl() =
project.messageBus.syncPublisher(MICROPYTHON_REPL_CONTROL).startOrRestartRepl()

private fun notifyProcessDestroyed() {
listeners.forEach { it.onProcessDestroyed() }
}
}

private fun notifyProcessCreationFailed(reason: String) {
listeners.forEach { it.onProcessCreationFailed(reason) }
}
}
val MICROPYTHON_REPL_CONTROL = Topic(MicroPythonReplControl::class.java)
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import com.intellij.execution.BeforeRunTaskProvider
import com.intellij.execution.configurations.RunConfiguration
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.LangDataKeys
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.module.Module
import com.intellij.openapi.components.service
import com.intellij.openapi.util.Key
import com.jetbrains.micropython.run.MicroPythonRunConfiguration
import com.jetbrains.micropython.settings.MicroPythonFacetType
Expand Down Expand Up @@ -38,21 +37,14 @@ class StopReplBeforeRunTaskProvider : BeforeRunTaskProvider<StopReplBeforeRunTas
}

override fun executeTask(
context: DataContext,
configuration: RunConfiguration,
environment: ExecutionEnvironment,
task: StopReplBeforeRunTask
context: DataContext,
configuration: RunConfiguration,
environment: ExecutionEnvironment,
task: StopReplBeforeRunTask
): Boolean {
if (configuration is MicroPythonRunConfiguration) {
ApplicationManager.getApplication().invokeLater {
configuration.module?.let {
ApplicationManager.getApplication().runWriteAction {
MicroPythonReplManager.getInstance(it).stopREPL()
}
}
}
environment.project.service<MicroPythonReplManager>().stopRepl()
}

return true
}

Expand Down
Loading