Skip to content

Commit

Permalink
Add support for uv init
Browse files Browse the repository at this point in the history
  • Loading branch information
InSyncWithFoo committed Sep 6, 2024
1 parent 58202ed commit f9ff9f2
Show file tree
Hide file tree
Showing 8 changed files with 300 additions and 91 deletions.
6 changes: 5 additions & 1 deletion src/main/kotlin/insyncwithfoo/ryecharm/Commands.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ internal abstract class CommandFactory {
workingDirectory = this@CommandFactory.workingDirectory
}

protected fun <T> T.add(vararg arguments: String) where T : Arguments, T : MutableList<String> {
this.addAll(arguments)
}

}


Expand Down Expand Up @@ -83,7 +87,7 @@ internal abstract class Command {
override fun toString() = commandLine.commandLineString

fun run(timeout: MillisecondsOrNoLimit): ProcessOutput {
LOGGER.info("Running: $this")
LOGGER.info("Running: ($workingDirectory) $this")

return processHandler.runProcess(timeout).also {
LOGGER.info("Output: ${ProcessOutputSurrogate(it)}")
Expand Down
27 changes: 25 additions & 2 deletions src/main/kotlin/insyncwithfoo/ryecharm/uv/commands/UV.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import insyncwithfoo.ryecharm.lastModified
import insyncwithfoo.ryecharm.path
import insyncwithfoo.ryecharm.rye.commands.Rye
import insyncwithfoo.ryecharm.rye.commands.homeDirectory
import insyncwithfoo.ryecharm.uv.generator.ProjectKind
import java.nio.file.Path
import kotlin.io.path.div
import kotlin.io.path.listDirectoryEntries
Expand Down Expand Up @@ -46,8 +47,30 @@ internal class UV private constructor(
override val workingDirectory: Path?
) : CommandFactory() {

fun init() =
InitCommand().build()
fun init(name: String?, kind: ProjectKind, createReadme: Boolean, pinPython: Boolean): Command {
val arguments = mutableListOf("--no-workspace")

if (name != null) {
arguments.add("--name")
arguments.add(name)
}

when (kind) {
ProjectKind.APP -> arguments.add("--app")
ProjectKind.LIBRARY -> arguments.add("--lib")
ProjectKind.PACKAGED_APP -> arguments.add("--app", "--package")
}

if (!createReadme) {
arguments.add("--no-readme")
}

if (!pinPython) {
arguments.add("--no-pin-python")
}

return InitCommand().build(arguments)
}

fun add(target: PythonPackageSpecification) =
AddCommand().build(arguments = listOf(target.toPEP508Format()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.intellij.openapi.vfs.VfsUtil
import com.intellij.platform.ProjectGeneratorPeer
import com.jetbrains.python.newProject.steps.PythonProjectSpecificSettingsStep
import insyncwithfoo.ryecharm.moduleManager
import insyncwithfoo.ryecharm.notifyIfProcessIsUnsuccessful
import insyncwithfoo.ryecharm.rootManager
import insyncwithfoo.ryecharm.runInForeground
import insyncwithfoo.ryecharm.uv.commands.uv
Expand All @@ -29,13 +30,6 @@ private fun Project.runInitializer(action: suspend CoroutineScope.() -> Unit) {
}


private fun UVProjectGenerator.makeSettings(settingsStep: UVProjectSettingsStep) =
projectSettings.apply {
sdk = settingsStep.sdk
interpreterInfoForStatistics = settingsStep.interpreterInfoForStatistics
}


private fun UVProjectGenerator.generateProject(
settingsStep: UVProjectSettingsStep,
settings: UVNewProjectSettings
Expand All @@ -48,11 +42,18 @@ private fun UVProjectGenerator.generateProject(
/**
* Run `uv init` at the newly created project directory.
*/
private fun Project.initializeUsingUV() {
val command = uv!!.init()
private fun Project.initializeUsingUV(settings: UVNewProjectSettings) {
val name = settings.distributionName
val kind = settings.projectKind
val createReadme = settings.createReadme
val pinPython = settings.pinPython

val command = uv!!.init(name, kind, createReadme, pinPython)

runInitializer {
runInForeground(command)
val output = runInForeground(command)

notifyIfProcessIsUnsuccessful(command, output)
}
}

Expand Down Expand Up @@ -90,15 +91,15 @@ internal class GenerateProjectCallback : AbstractCallback<UVNewProjectSettings>(
) {
val generator = (settingsStep as UVProjectSettingsStep).projectGenerator as UVProjectGenerator

val settings = generator.makeSettings(settingsStep)
val settings = settingsStep.settings
val newProject = generator.generateProject(settingsStep, settings)
?: error("Failed to generate project")

SdkConfigurationUtil.setDirectoryProjectSdk(newProject, settings.sdk!!)

newProject.initializeUsingUV()
newProject.initializeUsingUV(settings)

if (settingsStep.initializeGit) {
if (settings.initializeGit) {
newProject.initializeGitRepository()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,29 @@
package insyncwithfoo.ryecharm.uv.generator

import com.jetbrains.python.newProject.PyNewProjectSettings
import insyncwithfoo.ryecharm.Labeled
import insyncwithfoo.ryecharm.message
import insyncwithfoo.ryecharm.propertiesComponent


internal class UVNewProjectSettings : PyNewProjectSettings()
internal enum class ProjectKind(override val label: String) : Labeled {
APP(message("newProjectPanel.settings.projectKind.app")),
LIBRARY(message("newProjectPanel.settings.projectKind.library")),
PACKAGED_APP(message("newProjectPanel.settings.projectKind.packagedApp"));
}


internal class UVNewProjectSettings : PyNewProjectSettings() {
var initializeGit: Boolean
get() = propertiesComponent.getBoolean("PyCharm.NewProject.Git")
set(value) = propertiesComponent.setValue("PyCharm.NewProject.Git", value)

var distributionName: String? = null
set(value) {
field = value?.takeIf { it.isNotEmpty() }
}

var projectKind = ProjectKind.APP
var createReadme = true
var pinPython = true
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package insyncwithfoo.ryecharm.uv.generator

import com.intellij.facet.ui.ValidationResult
import com.intellij.ide.util.projectWizard.AbstractNewProjectStep.AbstractCallback
import com.intellij.ide.util.projectWizard.CustomStepProjectGenerator
import com.intellij.ide.util.projectWizard.ProjectSettingsStepBase
import com.intellij.platform.DirectoryProjectGenerator
import com.jetbrains.python.newProject.PythonProjectGenerator
import insyncwithfoo.ryecharm.icons.UVIcons
Expand All @@ -19,6 +21,9 @@ import insyncwithfoo.ryecharm.message
internal class UVProjectGenerator :
PythonProjectGenerator<UVNewProjectSettings>(), CustomStepProjectGenerator<UVNewProjectSettings> {

/**
* The displayed name of the panel.
*/
override fun getName() = message("newProjectPanel.title")

/**
Expand All @@ -27,9 +32,25 @@ internal class UVProjectGenerator :
*/
override fun getDescription() = null

/**
* The logo to be displayed beside the name of the panel.
*
* Size: 18&times;18
*
* It is supposed to be 16&times;16,
* but with the paddings it would look too small.
*/
override fun getLogo() = UVIcons.TINY_18

override fun getProjectSettings() = UVNewProjectSettings()
/**
* This method is supposed to be called by
* [ProjectSettingsStepBase.checkValid].
* However, [UVProjectSettingsStep.checkValid]
* does not use its super implementation.
*
* It is overridden here for documentation purposes only.
*/
override fun validate(baseDirPath: String) = ValidationResult.OK!!

override fun createStep(
projectGenerator: DirectoryProjectGenerator<UVNewProjectSettings>?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,84 @@ package insyncwithfoo.ryecharm.uv.generator

import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
import com.jetbrains.python.newProject.PyNewProjectSettings
import com.jetbrains.python.newProject.steps.ProjectSpecificSettingsStep
import com.jetbrains.python.newProject.steps.PythonProjectSpecificSettingsStep
import com.jetbrains.python.sdk.PyLazySdk
import com.jetbrains.python.sdk.add.v2.PythonAddNewEnvironmentPanel
import javax.swing.JPanel


// TODO: Refactor this
/**
* @see com.jetbrains.python.newProject.steps.PythonProjectSpecificSettingsStep
* @see PythonProjectSpecificSettingsStep
*/
internal class UVProjectSettingsStep(projectGenerator: UVProjectGenerator) :
ProjectSpecificSettingsStep<UVNewProjectSettings>(projectGenerator, GenerateProjectCallback()), DumbAware {

private val panel by lazy { UVProjectSettingsStepPanel(projectGenerator) }
val settings = UVNewProjectSettings()
get() = field.also { it.sdk = this.sdk }

private val panel by lazy { UVProjectSettingsStepPanel(settings) }

/**
* Generates a name for the new project (and its own directory).
* Generate a name for the new project (and its own directory).
*/
private val nextProjectDirectory by ::myProjectDirectory

val initializeGit: Boolean
get() = panel.initializeGit.get()
/**
* Used by various functions in [ProjectSpecificSettingsStep].
*/
private var projectLocationInput by ::myLocationField

/**
* @see PythonProjectSpecificSettingsStep.getProjectLocation
*/
override fun getProjectLocation() = panel.projectLocation

var projectLocationInput by ::myLocationField
/**
* Create the virtual environment and returns the SDK derived from that.
*
* @see com.jetbrains.python.sdk.add.v2.setupVirtualenv
*/
override fun getSdk() = PyLazySdk("Uninitialized environment") {
panel.venvCreator.createSdk()?.also { SdkConfigurationUtil.addSdk(it) }
?: error("Failed to create SDK")
}

/**
* Whether the example `main.py` script should be created.
* The original counterpart of this method,
* [PythonProjectSpecificSettingsStep.getInterpreterInfoForStatistics],
* is called by `PythonGenerateProjectCallback.computeProjectSettings`.
* The return value is then given to [PyNewProjectSettings].
*
* `PythonGenerateProjectCallback`'s monkeypatch,
* [GenerateProjectCallback], doesn't have a need for such information.
* This method is only overridden here for documentation purposes.
*
* Always return `false`, as this task is delegated to `uv.
* @see PythonAddNewEnvironmentPanel.createStatisticsInfo
*/
override fun createWelcomeScript() = false
override fun getInterpreterInfoForStatistics() = null

override fun getProjectLocation() = panel.projectLocation
/**
* @see getInterpreterInfoForStatistics
*/
override fun installFramework() = false

/**
* @see getInterpreterInfoForStatistics
*/
override fun createWelcomeScript() = false

/**
* @see getInterpreterInfoForStatistics
*/
override fun getRemotePath() = null

/**
* Create the panel and set up listeners.
*
* @see PythonProjectSpecificSettingsStep.createBasePanel
*/
override fun createBasePanel(): JPanel {
val panelComponent = recreateProjectCreationPanel()

Expand All @@ -50,9 +93,9 @@ internal class UVProjectSettingsStep(projectGenerator: UVProjectGenerator) :
}

/**
* Return the component created by [UVProjectSettingsStepPanel.makeComponent].
* Return the panel component created by
* [UVProjectSettingsStepPanel.makeComponent].
*
* @see com.jetbrains.python.newProject.steps.PythonProjectSpecificSettingsStep.createBasePanel
* @see com.jetbrains.python.sdk.add.v2.PythonAddNewEnvironmentPanel
*/
private fun recreateProjectCreationPanel() = panel.makeComponent()
Expand All @@ -78,14 +121,4 @@ internal class UVProjectSettingsStep(projectGenerator: UVProjectGenerator) :
*/
override fun onPanelSelected() {}

/**
* Create the virtual environment and returns the SDK derived from that.
*
* @see com.jetbrains.python.sdk.add.v2.setupVirtualenv
*/
override fun getSdk() = PyLazySdk("Uninitialized environment") {
panel.venvCreator.createSdk()?.also { SdkConfigurationUtil.addSdk(it) }
?: error("Failed to create SDK")
}

}
Loading

0 comments on commit f9ff9f2

Please sign in to comment.