Merge analyzer-update/rride/kt-252/KTIJ-34743 into 252

GitOrigin-RevId: c1bc4ccef086629e6968e03dcb52b46361be6f6b
This commit is contained in:
Patronus
2025-07-01 20:35:51 +00:00
committed by intellij-monorepo-bot
5 changed files with 122 additions and 16 deletions

View File

@@ -33,7 +33,6 @@ import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.NlsContexts
import com.intellij.openapi.util.Pair
import com.intellij.openapi.util.Weighted
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.vfs.FileIdAdapter
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.wm.FocusWatcher
@@ -230,6 +229,15 @@ open class EditorComposite internal constructor(
)
}
span("Artificially wait if the skeleton has been set recently to avoid flickering") {
compositePanel.skeleton?.let { editorSkeleton ->
val hasBeenShownFor = System.currentTimeMillis() - editorSkeleton.initialTime.get()
if (hasBeenShownFor < SKELETON_DELAY) {
delay(SKELETON_DELAY - hasBeenShownFor)
}
}
}
applyFileEditorsInEdt(
states = states,
fileEditorWithProviders = fileEditorWithProviders,
@@ -850,6 +858,8 @@ internal class EditorCompositePanel(@JvmField val composite: EditorComposite) :
private set
private val skeletonScope = composite.coroutineScope.childScope("Editor Skeleton")
var skeleton: EditorSkeleton? = null
private set
init {
addFocusListener(object : FocusAdapter() {
@@ -880,11 +890,7 @@ internal class EditorCompositePanel(@JvmField val composite: EditorComposite) :
if (EditorSkeletonPolicy.shouldShowSkeleton(composite)) {
skeletonScope.launch(Dispatchers.UI) {
delay(SKELETON_DELAY)
// show skeleton if editor is not added after [SKELETON_DELAY]
if (components.isEmpty()) {
add(EditorSkeleton(skeletonScope), BorderLayout.CENTER)
}
setNewSkeleton(EditorCompositeSkeletonFactory.getInstance(composite.project).createSkeleton(skeletonScope))
}
}
else {
@@ -892,6 +898,14 @@ internal class EditorCompositePanel(@JvmField val composite: EditorComposite) :
}
}
private fun setNewSkeleton(skeleton: EditorSkeleton?) {
this.skeleton = skeleton
if (skeleton == null) return
if (components.isEmpty()) {
add(skeleton, BorderLayout.CENTER)
}
}
override fun updateUI() {
super.updateUI()
@@ -933,11 +947,6 @@ internal class EditorCompositePanel(@JvmField val composite: EditorComposite) :
sink[CommonDataKeys.VIRTUAL_FILE] = composite.file
sink[CommonDataKeys.VIRTUAL_FILE_ARRAY] = arrayOf(composite.file)
}
companion object {
private val SKELETON_DELAY
get() = Registry.intValue("editor.skeleton.delay.ms", 300).toLong()
}
}
private class TopBottomPanel : JPanel() {

View File

@@ -0,0 +1,61 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.fileEditor.impl
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.registry.Registry
import com.intellij.util.AwaitCancellationAndInvoke
import com.intellij.util.awaitCancellationAndInvoke
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicLong
/**
* This service is responsible for creating and maintaining skeleton components for editor tabs
*
* Skeleton could be created in two cases:
* 1. If the editor is not created in [SKELETON_DELAY] ms
* 2. If the new tab (without an editor) is shown after a recently shown skeleton
*/
@Service(Service.Level.PROJECT)
internal class EditorCompositeSkeletonFactory(project: Project, private val scope: CoroutineScope) {
private val currentlyShownSkeleton = AtomicInteger(0)
private val initialTime: AtomicLong = AtomicLong(System.currentTimeMillis())
companion object {
fun getInstance(project: Project): EditorCompositeSkeletonFactory = project.service()
}
suspend fun createSkeleton(skeletonScope: CoroutineScope): EditorSkeleton? {
if (!Registry.`is`("editor.skeleton.animation.enabled")) return null
// if [currentlyShownSkeleton] equals 0, then there's no skeleton shown at the moment. We need to delay the skeleton creation to avoid flickering
if (currentlyShownSkeleton.get() == 0) {
delay(SKELETON_DELAY)
}
currentlyShownSkeleton.incrementAndGet()
initialTime.compareAndSet(-1, System.currentTimeMillis())
return doCreateSkeleton(skeletonScope)
}
@OptIn(AwaitCancellationAndInvoke::class)
private fun doCreateSkeleton(skeletonScope: CoroutineScope): EditorSkeleton {
skeletonScope.awaitCancellationAndInvoke {
scope.launch {
// delay actual deletion for skeleton to avoid flickering
delay(SKELETON_DELAY)
// If the last skeleton is removed, the process of tab jumping is completed and the phase could be moved to show an initial animation
if (currentlyShownSkeleton.decrementAndGet() == 0) {
initialTime.set(-1)
}
}
}
return EditorSkeleton(skeletonScope, initialTime)
}
}
internal val SKELETON_DELAY: Long
get() = Registry.intValue("editor.skeleton.delay.ms", 300).toLong()

View File

@@ -30,10 +30,9 @@ import kotlin.time.Duration.Companion.milliseconds
*
* Animation lasts while [cs] is active.
*/
internal class EditorSkeleton(cs: CoroutineScope) : JComponent() {
internal class EditorSkeleton(cs: CoroutineScope, val initialTime: AtomicLong) : JComponent() {
private val withAnimation = Registry.`is`("editor.skeleton.animation.enabled", true)
private val currentTime = AtomicLong(System.currentTimeMillis())
private val initialTime = currentTime.get()
init {
if (withAnimation) {
@@ -185,7 +184,7 @@ internal class EditorSkeleton(cs: CoroutineScope) : JComponent() {
return BACKGROUND_COLOR
}
val elapsed = currentTime.get() - initialTime
val elapsed = currentTime.get() - initialTime.get()
val t = (elapsed % ANIMATION_DURATION_MS).toDouble() / ANIMATION_DURATION_MS.toDouble()
val opacity = 0.3 + 0.3 * sin(2 * Math.PI * t)
return ColorUtil.withAlpha(BACKGROUND_COLOR, opacity)

View File

@@ -280,8 +280,9 @@ def patch_args(args):
# in practice it'd raise an exception here and would return original args, which is not what we want... providing
# a proper fix for https://youtrack.jetbrains.com/issue/PY-9767 elsewhere.
if i >= len(args) or _is_managed_arg(args[i]): # no need to add pydevd twice
log_debug("Patched args: %s" % str(args))
return args
new_args = quote_args(args)
log_debug("Patched args: %s" % str(new_args))
return new_args
for x in original:
new_args.append(x)

View File

@@ -7,6 +7,7 @@ try:
except ImportError:
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from _pydev_bundle import pydev_monkey
from pydevd import SetupHolder
from _pydev_bundle.pydev_monkey import pydev_src_dir
from _pydevd_bundle.pydevd_command_line_handling import get_pydevd_file
@@ -42,6 +43,22 @@ class TestCase(unittest.TestCase):
def test_str_to_args_windows(self):
self.assertEqual(['a', 'b'], pydev_monkey.str_to_args_windows('a "b"'))
self.assertEqual(['foo', 'bar'], pydev_monkey.str_to_args_windows('foo bar'))
self.assertEqual(['foo bar'], pydev_monkey.str_to_args_windows('"foo bar"'))
self.assertEqual(['foo"bar'], pydev_monkey.str_to_args_windows('"foo""bar"'))
self.assertEqual(['foo\\"bar'],
pydev_monkey.str_to_args_windows('"foo\\\\\\"bar"'))
self.assertEqual(['foo\\bar'], pydev_monkey.str_to_args_windows('foo\\bar'))
self.assertEqual(['arg one', 'arg two'],
pydev_monkey.str_to_args_windows('"arg one" "arg two"'))
# A string surrounded by double quote marks is interpreted as a single argument, whether it contains whitespace characters or not
# self.assertEqual([''], pydev_monkey.str_to_args_windows('""'))
self.assertEqual(['arg'], pydev_monkey.str_to_args_windows(' "arg" '))
self.assertEqual(['one', 'two three', 'four'],
pydev_monkey.str_to_args_windows('one "two three" four'))
# The double quote mark is interpreted as an escape sequence by the remaining backslash, causing a literal double quote mark (") to be placed in argv.
# Within a quoted string, a pair of double quote marks is interpreted as a single escaped double quote mark.
# self.assertEqual(['a"b"c'], pydev_monkey.str_to_args_windows('"a""b""c"'))
def test_monkey_patch_args_indc(self):
SetupHolder.setup = {'client': '127.0.0.1', 'port': '0'}
@@ -82,6 +99,25 @@ class TestCase(unittest.TestCase):
'test',
])
# PY-60819
@unittest.skipIf(sys.version_info < (3,),
"Test skipped on Python versions less than 3")
def test_monkey_patch_args_quotes_managed_path_windows(self):
from unittest.mock import patch
SetupHolder.setup = {'client': '127.0.0.1', 'port': '0'}
check = ['C:\\Python\\python.exe',
'"C:/path with spaces/pydevd.py"',]
with patch.object(pydev_monkey, 'is_python', return_value=True), \
patch('sys.platform', 'win32'):
expected = [
'C:\\Python\\python.exe',
'"C:/path with spaces/pydevd.py"',
]
actual = pydev_monkey.patch_args(check)
self.assertEqual(expected, actual)
def test_monkey_patch_args_no_indc(self):
SetupHolder.setup = {'client': '127.0.0.1', 'port': '0'}
check = ['C:\\bin\\python.exe', 'connect(\\"127.0.0.1\\")']