src/org/jetbrains/r/interpreter/RLocalInterpreterImpl.kt (118 lines of code) (raw):
/*
* Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/
package org.jetbrains.r.interpreter
import com.intellij.execution.process.ProcessHandler
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.asContextElement
import com.intellij.openapi.fileChooser.FileChooserDescriptor
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.fileChooser.FileChooserFactory
import com.intellij.openapi.fileChooser.ex.FileChooserDialogImpl
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.ComponentWithBrowseButton
import com.intellij.openapi.ui.TextComponentAccessor
import com.intellij.openapi.ui.TextFieldWithBrowseButton
import com.intellij.openapi.util.Version
import com.intellij.openapi.util.io.FileUtilRt
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.r.psi.RPluginCoroutineScope
import com.intellij.r.psi.interpreter.*
import com.intellij.r.psi.rinterop.RInterop
import com.intellij.util.system.CpuArch
import kotlinx.coroutines.async
import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.launch
import org.jetbrains.concurrency.Promise
import org.jetbrains.concurrency.asPromise
import org.jetbrains.r.rendering.toolwindow.RToolWindowFactory
import org.jetbrains.r.rinterop.RInteropImpl
import org.jetbrains.r.rinterop.RInteropUtil
import java.io.File
import java.nio.file.Files
import java.nio.file.Paths
class RLocalInterpreterProviderImpl : RLocalInterpreterProvider {
override fun instantiate(location: RLocalInterpreterLocation, project: Project): RInterpreterBase {
return RLocalInterpreterImpl(location, project)
}
}
class RLocalInterpreterImpl(location: RLocalInterpreterLocation, project: Project) : RInterpreterBase(location, project) {
private val interpreterPath = location.path
override val version: Version = RInterpreterUtil.getVersionByLocation(location) ?: throw RuntimeException("Invalid R interpreter")
private val versionInfo = RInterpreterUtil.loadInterpreterVersionInfo(location)
override val interpreterName: String get() = versionInfo["version.string"]?.replace(' ', '_') ?: "unnamed"
override val basePath = project.basePath!!
override val hostOS get() = OperatingSystem.current()
override val hostArch get() = CpuArch.CURRENT
override val interpreterPathOnHost get() = interpreterPath
override fun createRInteropForProcess(process: ProcessHandler, port: Int): RInteropImpl {
return RInteropUtil.createRInteropForLocalProcess(this, process, port)
}
override fun deleteFileOnHost(path: String) {
File(path).delete()
}
override fun uploadFileToHost(file: File, remoteDir: String) {
val target = Paths.get(remoteDir, file.name).toFile()
// Note: `copyTo()` calls `mkdirs()`
file.copyTo(target)
}
override fun uploadFileToHostIfNeeded(file: VirtualFile, preserveName: Boolean): String {
return file.path
}
override fun createFileChooserForHost(value: String, selectFolder: Boolean): TextFieldWithBrowseButton {
return TextFieldWithBrowseButton().also { component ->
component.text = value
val descriptor = if (selectFolder) {
FileChooserDescriptorFactory.createSingleFolderDescriptor()
} else {
FileChooserDescriptorFactory.createSingleFileDescriptor()
}
component.addActionListener(ComponentWithBrowseButton.BrowseFolderActionListener(
component, project, descriptor, TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT)
)
FileChooserFactory.getInstance().installFileCompletion(component.textField, descriptor, true, null)
}
}
override fun showFileChooserDialogForHost(selectFolder: Boolean): String? {
val descriptor = FileChooserDescriptor(!selectFolder, selectFolder, false, false, false, false)
val dialog = FileChooserDialogImpl(descriptor, project)
val choice = dialog.choose(project)
return choice.firstOrNull()?.path
}
override fun createTempFileOnHost(name: String, content: ByteArray?): String {
val i = name.indexOfLast { it == '.' }
val file = if (i == -1) {
createTempFileWithTimestamp(name, null)
} else {
createTempFileWithTimestamp(name.substring(0, i), name.substring(i))
}
content?.let { file.writeBytes(it) }
return file.path
}
private fun createTempFileWithTimestamp(name: String, extension: String?): File {
// Note: timestamps ensure the global uniqueness of file name in order to
// avoid any issues with VFS caching when outdated file contents might be read
return FileUtilRt.createTempFile("$name${System.currentTimeMillis()}", extension, true)
}
override fun createFileOnHost(name: String, content: ByteArray?, directory: String): String {
val path = Paths.get(directory, name)
path.toFile().parentFile.mkdirs()
Files.write(path, content ?: "".toByteArray())
LocalFileSystem.getInstance().refreshNioFiles(listOf(path))
return path.toString()
}
override fun createTempDirOnHost(name: String): String = FileUtilRt.createTempDirectory(name, null, true).path
override fun getGuaranteedWritableLibraryPath(libraryPaths: List<RInterpreterState.LibraryPath>, userPath: String): RInterpreter.PathWithInfo {
val writable = libraryPaths.find { it.isWritable }
return if (writable != null) {
RInterpreter.PathWithInfo(writable.path, isUserDirectoryCreated = false)
} else {
RInterpreter.PathWithInfo(userPath, isUserDirectoryCreated = File(userPath).mkdirs())
}
}
override fun showFileInViewer(rInterop: RInterop, pathOnHost: String): Promise<Unit> {
return RPluginCoroutineScope.getScope(project).async(ModalityState.defaultModalityState().asContextElement()) {
RToolWindowFactory.showFile(project, pathOnHost)
}.asCompletableFuture().asPromise()
}
override fun showUrlInViewer(rInterop: RInterop, url: String) {
RPluginCoroutineScope.getScope(project).launch(ModalityState.defaultModalityState().asContextElement()) {
RToolWindowFactory.showUrl(project, url)
}
}
}