mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IJPL-222854 support notebook documents in Python LSP
Route Jupyter notebooks through the LSP notebook protocol so Python language servers work with cell subdocuments instead of the whole .ipynb file. The adapter maps host positions and ranges to cell URIs, sends notebook open/change/close/save notifications, and keeps python-lsp-core decoupled from Jupyter through a notebook language resolver EP. Register the Jupyter resolver from the Python/Jupyter plugin and let Python LSP descriptors opt into notebook-backed files. Skip Ty pull diagnostics for notebooks until Ty supports notebook diagnostics, while keeping diagnostics enabled for regular Python files. Add Ty and Pyrefly notebook LSP environment coverage for protocol notifications, code insight requests, range mapping, type inference, and the Ty diagnostics regression. Fix the Python test-env UV bootstrap used by that coverage by handling flat Windows zip archives separately from Unix archives with a root directory. GitOrigin-RevId: f958d48142cc376473a2be47b618e00366643f18
This commit is contained in:
committed by
intellij-monorepo-bot
parent
d9fea38eb3
commit
76a5a5e7fd
@@ -23,6 +23,10 @@ jvm_library(
|
||||
),
|
||||
kotlinc_opts = ":custom_testFramework",
|
||||
module_name = "intellij.notebooks.visualization.testFramework",
|
||||
resource_strip_prefix = "resources",
|
||||
resources = glob(
|
||||
["resources/**/*"],
|
||||
),
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
# do not sort,
|
||||
|
||||
+1
@@ -24,6 +24,7 @@
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<idea-plugin visibility="internal">
|
||||
<dependencies>
|
||||
<!-- region Generated dependencies - run `Generate Product Layouts` to regenerate -->
|
||||
<module name="intellij.libraries.assertj.core"/>
|
||||
<module name="intellij.libraries.gson"/>
|
||||
<module name="intellij.libraries.kotlinx.serialization.core"/>
|
||||
<module name="intellij.markdown"/>
|
||||
<module name="intellij.notebooks.jupyter.core"/>
|
||||
<module name="intellij.notebooks.ui"/>
|
||||
<module name="intellij.notebooks.visualization"/>
|
||||
<module name="intellij.platform.analysis"/>
|
||||
<module name="intellij.platform.codeStyle"/>
|
||||
<module name="intellij.platform.core"/>
|
||||
<module name="intellij.platform.core.impl"/>
|
||||
<module name="intellij.platform.core.ui"/>
|
||||
<module name="intellij.platform.editor.ex"/>
|
||||
<module name="intellij.platform.editor.ui"/>
|
||||
<module name="intellij.platform.ide.impl"/>
|
||||
<module name="intellij.platform.lang"/>
|
||||
<module name="intellij.platform.lang.impl"/>
|
||||
<module name="intellij.platform.projectModel"/>
|
||||
<module name="intellij.platform.testFramework"/>
|
||||
<module name="intellij.platform.util.coroutines"/>
|
||||
<!-- endregion -->
|
||||
</dependencies>
|
||||
</idea-plugin>
|
||||
+6
-7
@@ -97,16 +97,15 @@ abstract class PyEnvironmentProvider<S : PyEnvironmentSpec<S>>(
|
||||
abstract suspend fun setupEnvironment(context: Context, spec: S): PyEnvironment
|
||||
|
||||
/**
|
||||
* Extract archive using Decompressor framework.
|
||||
* Extract archive using the root directory used by Python archives.
|
||||
* Supports tar.gz, .tgz, and .zip formats.
|
||||
*
|
||||
*
|
||||
* @param archiveFile Path to the archive file
|
||||
* @param targetDir Directory where archive contents will be extracted
|
||||
* @param prefixToStrip Path prefix to strip during extraction (e.g., "uv-x86_64-unknown-linux-gnu" for UV archives)
|
||||
* @throws IllegalArgumentException if archive format is not supported
|
||||
*/
|
||||
protected fun unpackArchive(archiveFile: Path, targetDir: Path, prefixToStrip: String? = null) {
|
||||
com.intellij.python.test.env.core.unpackArchive(archiveFile, targetDir, prefixToStrip)
|
||||
protected fun unpackArchive(archiveFile: Path, targetDir: Path) {
|
||||
unpackArchive(archiveFile, targetDir, prefixToStrip = "python")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,13 +118,13 @@ abstract class PyEnvironmentProvider<S : PyEnvironmentSpec<S>>(
|
||||
* Can be used to create nested or dependent environments.
|
||||
*/
|
||||
val factory: PyEnvironmentFactory
|
||||
|
||||
|
||||
/**
|
||||
* Working directory where environments are created.
|
||||
* Each environment typically gets its own subdirectory.
|
||||
*/
|
||||
val workingDir: Path
|
||||
|
||||
|
||||
/**
|
||||
* Cache directory for storing downloaded artifacts.
|
||||
* Used to avoid re-downloading Python distributions and other resources.
|
||||
|
||||
+4
-4
@@ -2,6 +2,7 @@ package com.intellij.python.test.env.core
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.util.io.Decompressor
|
||||
import com.intellij.util.system.LowLevelLocalMachineAccess
|
||||
import com.intellij.util.system.OS
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -91,6 +92,7 @@ suspend fun installPipPackages(
|
||||
executeProcess(command, logger, "pip")
|
||||
}
|
||||
|
||||
@OptIn(LowLevelLocalMachineAccess::class)
|
||||
fun markExecutable(logger: Logger, executable: Path) {
|
||||
if (OS.CURRENT != OS.Windows) {
|
||||
logger.info("Setting executable permissions")
|
||||
@@ -111,16 +113,15 @@ fun markExecutable(logger: Logger, executable: Path) {
|
||||
*
|
||||
* @param archiveFile Path to the archive file
|
||||
* @param targetDir Directory where archive contents will be extracted
|
||||
* @param prefixToStrip Path prefix to strip during extraction (e.g., "uv-x86_64-unknown-linux-gnu" for UV archives)
|
||||
* @param prefixToStrip Path prefix to strip during extraction. If null, archive paths are extracted as-is.
|
||||
* @throws IllegalStateException if archive format is not supported
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
fun unpackArchive(archiveFile: Path, targetDir: Path, prefixToStrip: String? = null) {
|
||||
fun unpackArchive(archiveFile: Path, targetDir: Path, prefixToStrip: String?) {
|
||||
val fileName = archiveFile.fileName.toString()
|
||||
when {
|
||||
fileName.endsWith(".tar.gz", ignoreCase = true) || fileName.endsWith(".tgz", ignoreCase = true) -> {
|
||||
val decompressor = Decompressor.Tar(archiveFile)
|
||||
.removePrefixPath("python")
|
||||
if (prefixToStrip != null) {
|
||||
decompressor.removePrefixPath(prefixToStrip)
|
||||
}
|
||||
@@ -128,7 +129,6 @@ fun unpackArchive(archiveFile: Path, targetDir: Path, prefixToStrip: String? = n
|
||||
}
|
||||
fileName.endsWith(".zip", ignoreCase = true) -> {
|
||||
val decompressor = Decompressor.Zip(archiveFile).withZipExtensions()
|
||||
.removePrefixPath("python")
|
||||
if (prefixToStrip != null) {
|
||||
decompressor.removePrefixPath(prefixToStrip)
|
||||
}
|
||||
|
||||
Vendored
+8
-4
@@ -6,9 +6,9 @@ import com.intellij.openapi.application.writeAction
|
||||
import com.intellij.openapi.projectRoots.ProjectJdkTable
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.python.test.env.common.PredefinedPyEnvironments
|
||||
import com.intellij.python.test.env.common.createEnvironment
|
||||
import com.intellij.python.test.env.core.PyEnvironment
|
||||
import com.intellij.python.test.env.core.PyEnvironmentFactory
|
||||
import com.intellij.python.test.env.core.PyEnvironmentSpec
|
||||
import com.intellij.python.test.env.junit5.RunOnEnvironmentsExtension
|
||||
import com.intellij.python.test.env.junit5.getOrCreatePyEnvironmentFactory
|
||||
import com.intellij.testFramework.junit5.fixture.TestFixture
|
||||
@@ -31,16 +31,20 @@ fun pySdkFixture(): TestFixture<SdkFixture<PyEnvironment>> = testFixture { conte
|
||||
*/
|
||||
fun pySdkFixture(
|
||||
env: PredefinedPyEnvironments,
|
||||
): TestFixture<SdkFixture<PyEnvironment>> = pySdkFixture(env.spec)
|
||||
|
||||
fun pySdkFixture(
|
||||
envSpec: PyEnvironmentSpec<*>,
|
||||
): TestFixture<SdkFixture<PyEnvironment>> = testFixture { context ->
|
||||
val factory = getOrCreatePyEnvironmentFactory(context.extensionContext)
|
||||
initializedPySdkFixture(factory, env)
|
||||
initializedPySdkFixture(factory, envSpec)
|
||||
}
|
||||
|
||||
private suspend fun TestFixtureInitializer.R<SdkFixture<PyEnvironment>>.initializedPySdkFixture(
|
||||
factory: PyEnvironmentFactory,
|
||||
env: PredefinedPyEnvironments,
|
||||
envSpec: PyEnvironmentSpec<*>,
|
||||
): TestFixtureInitializer.InitializedTestFixture<SdkFixture<PyEnvironment>> {
|
||||
val env = factory.createEnvironment(env)
|
||||
val env = factory.createEnvironment(envSpec)
|
||||
return initializedTestFixture(env)
|
||||
}
|
||||
|
||||
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.python.test.env.junit5
|
||||
|
||||
import com.intellij.python.test.env.uv.getUvArchivePrefixToStrip
|
||||
import com.intellij.util.system.OS
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
internal class UvExecutableProviderTest {
|
||||
@Test
|
||||
fun `Windows uv archive is extracted without prefix stripping`() {
|
||||
assertNull(getUvArchivePrefixToStrip(OS.Windows, "uv-x86_64-pc-windows-msvc.zip"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Unix uv archive is extracted from its root directory`() {
|
||||
assertEquals("uv-x86_64-unknown-linux-gnu", getUvArchivePrefixToStrip(OS.Linux, "uv-x86_64-unknown-linux-gnu.tar.gz"))
|
||||
}
|
||||
}
|
||||
+16
-5
@@ -7,6 +7,7 @@ import com.intellij.python.test.env.core.extractIfNecessary
|
||||
import com.intellij.python.test.env.core.markExecutable
|
||||
import com.intellij.python.test.env.core.unpackArchive
|
||||
import com.intellij.util.system.CpuArch
|
||||
import com.intellij.util.system.LowLevelLocalMachineAccess
|
||||
import com.intellij.util.system.OS
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.nio.file.Path
|
||||
@@ -22,12 +23,14 @@ private val LOG = Logger.getInstance("com.intellij.python.test.env.uv")
|
||||
* @return Path to the UV executable
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
@OptIn(LowLevelLocalMachineAccess::class)
|
||||
suspend fun getOrDownloadUvExecutable(uvVersion: String): Path {
|
||||
val logger = LOG
|
||||
|
||||
val downloadUrl = getUvDownloadUrl(uvVersion)
|
||||
val archiveFileName = downloadUrl.substringAfterLast('/')
|
||||
val archiveBaseName = archiveFileName.substringBeforeLast(".tar.gz").substringBeforeLast(".zip")
|
||||
val prefixToStrip = getUvArchivePrefixToStrip(OS.CURRENT, archiveFileName)
|
||||
val uvDir = PyEnvDownloadCache.cacheDirectory().resolve("uv").resolve(archiveBaseName)
|
||||
val uvExecutable = uvDir.resolve(if (OS.CURRENT == OS.Windows) "uv.exe" else "uv")
|
||||
|
||||
@@ -35,14 +38,20 @@ suspend fun getOrDownloadUvExecutable(uvVersion: String): Path {
|
||||
logger.info("Downloading UV archive: $archiveFileName")
|
||||
val cachedArchive = PyEnvDownloadCache.getOrDownload(downloadUrl, archiveFileName)
|
||||
logger.info("UV archive downloaded: $cachedArchive")
|
||||
logger.info("Extracting UV binary (stripping prefix: $archiveBaseName)")
|
||||
unpackArchive(cachedArchive, target, prefixToStrip = archiveBaseName)
|
||||
logger.info("Extracting UV binary (stripping prefix: $prefixToStrip)")
|
||||
unpackArchive(cachedArchive, target, prefixToStrip = prefixToStrip)
|
||||
markExecutable(logger, uvExecutable)
|
||||
}
|
||||
|
||||
return uvExecutable
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
fun getUvArchivePrefixToStrip(os: OS, archiveFileName: String): String? {
|
||||
return if (os == OS.Windows) null else archiveFileName.substringBeforeLast(".tar.gz").substringBeforeLast(".zip")
|
||||
}
|
||||
|
||||
@OptIn(LowLevelLocalMachineAccess::class)
|
||||
private fun getUvDownloadUrl(version: String): String {
|
||||
val arch = when {
|
||||
CpuArch.isArm64() -> "aarch64"
|
||||
@@ -50,12 +59,14 @@ private fun getUvDownloadUrl(version: String): String {
|
||||
else -> "x86_64"
|
||||
}
|
||||
|
||||
val platform = when (OS.CURRENT) {
|
||||
val os = OS.CURRENT
|
||||
val platform = when (os) {
|
||||
OS.Windows -> "$arch-pc-windows-msvc"
|
||||
OS.macOS -> "$arch-apple-darwin"
|
||||
OS.Linux -> "$arch-unknown-linux-gnu"
|
||||
else -> throw IllegalStateException("Unsupported OS: ${OS.CURRENT}")
|
||||
else -> throw IllegalStateException("Unsupported OS: $os")
|
||||
}
|
||||
val extension = if (os == OS.Windows) "zip" else "tar.gz"
|
||||
|
||||
return "$UV_RELEASES_URL/$version/uv-$platform.tar.gz"
|
||||
return "$UV_RELEASES_URL/$version/uv-$platform.$extension"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user