mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-05 01:50:56 +07:00
Merge analyzer-update/rride/kt-252/KTIJ-34743 into 252
GitOrigin-RevId: c1bc4ccef086629e6968e03dcb52b46361be6f6b
This commit is contained in:
@@ -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() {
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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\\")']
|
||||
|
||||
Reference in New Issue
Block a user