OPENIDE #85 Add welcome screen for openide

(cherry picked from commit 216f7d0bd79089bb6dc70ce76032a7652f90c294)
This commit is contained in:
Nikita Iarychenko
2025-02-28 11:39:35 +04:00
parent 9a3394e66a
commit e39da2aa11
15 changed files with 791 additions and 3 deletions

View File

@@ -45,8 +45,6 @@ class OpenIdeExternalResourceUrls : ExternalProductResourceUrls {
return productUrl.resolve("ide/docs/OpenIDE_ReferenceCard$suffix.pdf")
}
override val whatIsNewPageUrl = productUrl.resolve("whatsnew")
override val gettingStartedPageUrl = productUrl.resolve("ide/resources")
override val helpPageUrl: ((topicId: String) -> Url)

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) Haulmont 2024. All Rights Reserved.
* Use is subject to license terms.
*/
package ru.openide.action
import ru.openide.welcome.screen.WelcomeScreenHelper
import com.intellij.ide.IdeBundle
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.application.ApplicationNamesInfo
import com.intellij.openapi.project.DumbAware
class OpenIdeWelcomeScreenOpenAction : AnAction(), DumbAware {
override fun update(e: AnActionEvent) {
e.presentation.isEnabledAndVisible = true
e.presentation.setText(IdeBundle.messagePointer("whats.new.action.custom.text", ApplicationNamesInfo.getInstance().fullProductName))
e.presentation.setDescription(IdeBundle.messagePointer("whats.new.action.custom.description", ApplicationNamesInfo.getInstance().fullProductName))
}
override fun getActionUpdateThread() = ActionUpdateThread.BGT
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
WelcomeScreenHelper(project).showWelcomeScreen()
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) Haulmont 2024. All Rights Reserved.
* Use is subject to license terms.
*/
package ru.openide.html
import ru.openide.welcome.screen.WelcomeScreenHelper
import com.intellij.openapi.fileEditor.*
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.ui.jcef.JBCefApp
import com.intellij.util.application
class HtmlEditorProvider : FileEditorProvider, DumbAware {
override fun createEditor(project: Project, file: VirtualFile): FileEditor {
val model = MODEL_KEY.get(file)
?: WelcomeScreenHelper(project).createModel(file)!!.also { MODEL_KEY.set(file, it) }
return HtmlFileEditor(project, file, model)
}
override fun accept(project: Project, file: VirtualFile): Boolean = file.getUserData(MODEL_KEY) != null
override fun acceptRequiresReadAction(): Boolean = false
override fun getEditorTypeId(): String = "openide-html-editor"
override fun getPolicy(): FileEditorPolicy = FileEditorPolicy.HIDE_DEFAULT_EDITOR
@Suppress("CompanionObjectInExtension")
companion object {
val MODEL_KEY: Key<CefEditorModel> = Key.create("openide.html.editor.model.key")
fun openEditor(project: Project, model: CefEditorModel, file: VirtualFile) {
if (!JBCefApp.isSupported()) return
MODEL_KEY.set(file, model)
application.invokeLaterOnWriteThread {
FileEditorManager.getInstance(project)
.openEditor(OpenFileDescriptor(project, file), true)
}
}
}
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) Haulmont 2024. All Rights Reserved.
* Use is subject to license terms.
*/
package ru.openide.html
import com.intellij.ide.fileTemplates.FileTemplateManager
import com.intellij.openapi.project.Project
class HtmlEditorTemplate(val name: String) {
private val params = mutableMapOf<String, Any>()
fun addParam(name: String, value: Any) = params.put(name, value)
fun createTextFromInternal(project: Project): String {
val templateManager = FileTemplateManager.getInstance(project)
val template = templateManager.getInternalTemplate(name)
val props = templateManager.defaultProperties
props.putAll(params)
return template.getText(props)
}
}

View File

@@ -0,0 +1,259 @@
/*
* Copyright (c) Haulmont 2024. All Rights Reserved.
* Use is subject to license terms.
*/
package ru.openide.html
import com.intellij.CommonBundle
import com.intellij.ide.IdeBundle
import com.intellij.ide.plugins.MultiPanel
import com.intellij.openapi.application.ApplicationInfo
import com.intellij.openapi.application.invokeLater
import com.intellij.openapi.editor.EditorBundle
import com.intellij.openapi.fileEditor.FileEditor
import com.intellij.openapi.fileEditor.FileEditorState
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.ActionCallback
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.util.SystemInfoRt.isLinux
import com.intellij.openapi.util.UserDataHolderBase
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.util.registry.RegistryManager
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.wm.StatusBar
import com.intellij.ui.components.JBLoadingPanel
import com.intellij.ui.jcef.JBCefAppArmorUtils
import com.intellij.ui.jcef.JBCefBrowserBase
import com.intellij.ui.jcef.JCEFHtmlPanel
import com.intellij.util.Alarm
import com.intellij.util.SystemProperties
import com.intellij.util.io.URLUtil
import com.intellij.util.ui.UIUtil
import org.cef.browser.CefBrowser
import org.cef.browser.CefFrame
import org.cef.browser.CefMessageRouter
import org.cef.callback.CefQueryCallback
import org.cef.handler.*
import org.cef.network.CefRequest
import org.jetbrains.concurrency.runAsync
import java.awt.BorderLayout
import java.beans.PropertyChangeListener
import java.util.concurrent.atomic.AtomicBoolean
import javax.swing.JComponent
class HtmlFileEditor(
private val project: Project,
private val file: VirtualFile,
private val model: CefEditorModel,
) : UserDataHolderBase(), FileEditor {
private val loadingPanel = JBLoadingPanel(BorderLayout(), this)
private lateinit var contentPanel: JCEFHtmlPanel
private val alarm = Alarm(Alarm.ThreadToUse.SWING_THREAD, this)
private val initial = AtomicBoolean(true)
private val navigating = AtomicBoolean(false)
private val multiPanel = object : MultiPanel() {
override fun create(key: Int): JComponent = when (key) {
LOADING_KEY -> loadingPanel
CONTENT_KEY -> contentPanel.component
LINUX_APP_ARMOR_KEY -> JBCefAppArmorUtils.getUnprivilegedUserNamespacesRestrictedStubPanel()
else -> throw IllegalArgumentException("Unknown key: $key")
}
override fun select(key: Int, now: Boolean): ActionCallback {
val callback = super.select(key, now)
if (key == CONTENT_KEY) {
UIUtil.invokeLaterIfNeeded { contentPanel.component.requestFocusInWindow() }
}
return callback
}
}
init {
loadingPanel.setLoadingText(CommonBundle.getLoadingTreeNodeText())
if (needToFixLinuxCef()) {
multiPanel.select(LINUX_APP_ARMOR_KEY, true)
}
else {
initContentPanel()
}
}
override fun getComponent(): JComponent = multiPanel
override fun getPreferredFocusedComponent(): JComponent = multiPanel
override fun getName(): String = IdeBundle.message("tab.title.html.preview")
override fun setState(state: FileEditorState) {}
override fun isModified(): Boolean = false
override fun isValid(): Boolean = true
override fun addPropertyChangeListener(listener: PropertyChangeListener) {}
override fun removePropertyChangeListener(listener: PropertyChangeListener) {}
override fun dispose() {}
override fun getFile(): VirtualFile = file
//https://youtrack.haulmont.com/issue/ASPR-1589
private fun needToFixLinuxCef() =
ApplicationInfo.getInstance().build.baselineVersion < 242 && isLinux && useSandbox() && JBCefAppArmorUtils.areUnprivilegedUserNamespacesRestricted()
private fun initContentPanel() {
contentPanel = JCEFHtmlPanel(true, null, null)
val client = contentPanel.jbCefClient
val cefBrowser = contentPanel.cefBrowser
client.addLoadHandler(object : CefLoadHandlerAdapter() {
override fun onLoadingStateChange(
browser: CefBrowser,
isLoading: Boolean,
canGoBack: Boolean,
canGoForward: Boolean,
) {
if (initial.get()) {
if (isLoading) {
invokeLater(runnable = ::startLoading)
}
else {
alarm.cancelAllRequests()
initial.set(false)
invokeLater(runnable = ::stopLoading)
}
}
}
}, cefBrowser)
client.addDisplayHandler(object : CefDisplayHandlerAdapter() {
override fun onStatusMessage(browser: CefBrowser, text: String) {
if (!text.startsWith(LOCAL_URL)) {
StatusBar.Info.set(text, project)
}
}
}, cefBrowser)
contentPanel.setErrorPage { errorCode, errorText, failedUrl ->
if (errorCode == CefLoadHandler.ErrorCode.ERR_ABORTED && navigating.getAndSet(false)) null
else JBCefBrowserBase.ErrorPage.DEFAULT.create(errorCode, errorText, failedUrl)
}
if (SystemInfo.isLinux) {
//remove default Intellij focusHandler on linux because of irretrievable focus interception
client.cefClient.removeFocusHandler()
}
multiPanel.select(CONTENT_KEY, true)
if (model is AsyncLoadCefEditorModel) {
startLoading()
runAsync { model.modelLoader.invoke() }.onProcessed {
if (it != null) {
initClient(it)
loadContent(it)
}
}
}
else {
initClient(model)
loadContent(model)
}
}
private fun loadContent(model: CefEditorModel) {
when (model) {
is HtmlCefEditorModel -> {
contentPanel.loadHTML(model.html, LOCAL_URL)
}
is UrlCefEditorModel -> {
val timeoutText = model.timeoutHtml ?: EditorBundle.message("message.html.editor.timeout")
alarm.addRequest(
{ contentPanel.loadHTML(timeoutText) },
Registry.intValue("html.editor.timeout", URL_LOADING_TIMEOUT_MS)
)
contentPanel.loadURL(model.url)
}
is AsyncLoadCefEditorModel -> {
startLoading()
runAsync { model.modelLoader.invoke() }.onProcessed {
if (it != null) {
loadContent(it)
}
}
}
}
}
fun useSandbox() = SystemProperties.getBooleanProperty("jcef.use_sandbox", true)
&& RegistryManager.getInstance().get("ide.browser.jcef.sandbox.enable").asBoolean()
private fun initClient(model: CefEditorModel) {
val client = contentPanel.jbCefClient
val cefBrowser = contentPanel.cefBrowser
client.addRequestHandler(object : CefRequestHandlerAdapter() {
override fun onBeforeBrowse(
cefBrowser: CefBrowser,
cefFrame: CefFrame,
cefRequest: CefRequest,
userGesture: Boolean,
isRedirect: Boolean,
) = if (userGesture) {
navigating.set(true)
model.browseHandler(BrowseRequest(cefBrowser, cefFrame, cefRequest))
}
else {
false
}
}, cefBrowser)
client.addLifeSpanHandler(object : CefLifeSpanHandlerAdapter() {
override fun onBeforePopup(
cefBrowser: CefBrowser,
cefFrame: CefFrame,
targetUrl: String,
targetFrameName: String?,
) = model.browseHandler(BrowseRequest(cefBrowser, cefFrame, targetUrl))
}, cefBrowser)
model.queries.groupBy(JsQuery::name).forEach { (name, queries) ->
val config = CefMessageRouter.CefMessageRouterConfig(name, "${name}Cancel")
val jsRouter = CefMessageRouter.create(config)
jsRouter.addHandler(object : CefMessageRouterHandlerAdapter() {
override fun onQuery(
cefBrowser: CefBrowser,
cefFrame: CefFrame,
id: Long,
request: String?,
persistent: Boolean,
callback: CefQueryCallback,
): Boolean {
val query = queries.firstOrNull { it.request == request } ?: return false
query.handler(JsQueryRequest(cefBrowser, cefFrame, callback))
return true
}
}, true)
client.cefClient.addMessageRouter(jsRouter)
}
}
private fun startLoading() {
if (!loadingPanel.isLoading) {
loadingPanel.startLoading()
}
multiPanel.select(LOADING_KEY, true)
}
private fun stopLoading() {
if (loadingPanel.isLoading) {
loadingPanel.stopLoading()
}
multiPanel.select(CONTENT_KEY, true)
}
private companion object {
private const val LINUX_APP_ARMOR_KEY = 2
private const val LOADING_KEY = 1
private const val CONTENT_KEY = 0
private const val URL_LOADING_TIMEOUT_MS = 10000
private const val LOCAL_URL = "${URLUtil.FILE_PROTOCOL}:///openide"
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) Haulmont 2024. All Rights Reserved.
* Use is subject to license terms.
*/
package ru.openide.html
import com.intellij.ide.BrowserUtil
import com.intellij.openapi.project.Project
import org.cef.browser.CefBrowser
import org.cef.browser.CefFrame
import org.cef.callback.CefQueryCallback
import org.cef.network.CefRequest
import java.io.InputStream
abstract class CefEditorModel {
var browseHandler: (BrowseRequest) -> Boolean = ::defaultBrowseHandle
val queries = mutableListOf<JsQuery>()
var project: Project? = null
fun defaultBrowseHandle(request: BrowseRequest): Boolean {
BrowserUtil.browse(request.cefRequest.url)
return true
}
fun addJsQuery(name: String, request: String, handler: (JsQueryRequest) -> Unit) {
queries.add(JsQuery(name, request, handler))
}
}
class UrlCefEditorModel(val url: String, var timeoutHtml: String? = null) : CefEditorModel()
class HtmlCefEditorModel(val html: String) : CefEditorModel()
class AsyncLoadCefEditorModel(val modelLoader: () -> CefEditorModel) : CefEditorModel()
class BrowseRequest(
val browser: CefBrowser,
val frame: CefFrame,
val cefRequest: CefRequest,
) {
constructor(browser: CefBrowser, frame: CefFrame, url: String) : this(
browser,
frame,
CefRequest.create().apply { this.url = url }
)
}
class JsQuery(
val name: String,
val request: String,
val handler: (JsQueryRequest) -> Unit,
)
class JsQueryRequest(
val browser: CefBrowser,
val frame: CefFrame,
val callback: CefQueryCallback,
)
abstract class Resource(val mimeType: String?) {
abstract fun getInputStream(): InputStream?
}

View File

@@ -0,0 +1,25 @@
// Copyright (c) Haulmont 2025. All Rights Reserved.
// Use is subject to license terms.
package ru.openide.welcome.screen
import com.intellij.openapi.components.*
import com.intellij.openapi.project.Project
import com.intellij.util.xmlb.XmlSerializerUtil
@Service(Service.Level.PROJECT)
@State(name = "OpenIdeProjectLocalState", storages = [Storage(StoragePathMacros.WORKSPACE_FILE)])
class OpenIdeProjectLocalState : PersistentStateComponent<OpenIdeProjectLocalState> {
companion object {
fun getInstance(project: Project): OpenIdeProjectLocalState = project.getService(OpenIdeProjectLocalState::class.java)
}
var isFirstOpen: Boolean = true
override fun getState(): OpenIdeProjectLocalState = this
override fun loadState(state: OpenIdeProjectLocalState) {
XmlSerializerUtil.copyBean(state, this)
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) Haulmont 2024. All Rights Reserved.
* Use is subject to license terms.
*/
package ru.openide.welcome.screen
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.extensions.ExtensionNotApplicableException
import com.intellij.openapi.project.DumbAwareRunnable
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.ProjectActivity
import com.intellij.openapi.startup.StartupManager
import com.intellij.util.concurrency.AppExecutorUtil
import java.util.concurrent.Callable
class OpenIdeWelcomeScreenProjectActivity : ProjectActivity {
init {
val app = ApplicationManager.getApplication()
if (app.isCommandLine || app.isHeadlessEnvironment || app.isUnitTestMode) {
throw ExtensionNotApplicableException.create()
}
}
override suspend fun execute(project: Project) = StartupManager.getInstance(project).runAfterOpened(
DumbAwareRunnable {
ApplicationManager.getApplication().invokeLater(
{
DumbService.getInstance(project).smartInvokeLater {
smartOpenedAction(project)
}
}
) { !project.isOpen || project.isDisposed }
}
)
private fun smartOpenedAction(project: Project) {
ReadAction.nonBlocking((Callable {
OpenIdeProjectLocalState.getInstance(project).isFirstOpen
}))
.inSmartMode(project)
.finishOnUiThread(ModalityState.any()) { showWelcomeScreen(project, it) }
.coalesceBy(javaClass, project)
.submit(AppExecutorUtil.getAppExecutorService())
}
private fun showWelcomeScreen(project: Project, isFirstOpen: Boolean) {
if (!isFirstOpen) return
OpenIdeProjectLocalState.getInstance(project).isFirstOpen = false
WelcomeScreenHelper(project).showWelcomeScreen()
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright (c) Haulmont 2024. All Rights Reserved.
* Use is subject to license terms.
*/
package ru.openide.welcome.screen
import com.intellij.ide.plugins.PluginManagerConfigurable
import ru.openide.welcome.screen.editor.WelcomeScreenFile
import ru.openide.welcome.screen.editor.WelcomeScreenFileSystem
import ru.openide.welcome.screen.editor.WelcomeScreenFileType
import com.intellij.openapi.application.invokeLater
import com.intellij.openapi.extensions.PluginId
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.OpenFileDescriptor
import com.intellij.openapi.options.ShowSettingsUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.guessProjectDir
import com.intellij.openapi.updateSettings.impl.pluginsAdvertisement.installAndEnable
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.ui.EditorNotifications
import com.intellij.ui.jcef.JBCefApp
import com.intellij.util.ui.StartupUiUtil
import ru.openide.html.*
class WelcomeScreenHelper(val project: Project) {
fun showWelcomeScreen() {
if (!JBCefApp.isSupported()) return
val editorManager = FileEditorManager.getInstance(project)
val openedWelcome = editorManager.allEditors.firstOrNull(WelcomeScreenFileType::isMyEditor)
if (openedWelcome != null) {
editorManager.openEditor(OpenFileDescriptor(project, openedWelcome.file), true)
return
}
val file = WelcomeScreenFileSystem.getFile(project) ?: WelcomeScreenFile(project.guessProjectDir())
val editorModel = createModel(file) ?: return
HtmlEditorProvider.openEditor(project, editorModel, file)
}
fun createModel(file: VirtualFile): HtmlCefEditorModel? {
if (!WelcomeScreenFileType.isMyFile(file)) return null
val htmlTemplate = HtmlEditorTemplate(TEMPLATE_NAME).apply {
addParam(THEME, if (StartupUiUtil.isDarkTheme) "theme-dark" else "")
}
return createEditorModel(htmlTemplate.createTextFromInternal(project))
}
private fun createEditorModel(html: String) = HtmlCefEditorModel(html).apply {
project = this@WelcomeScreenHelper.project
addJsQuery(JsQuery.OPEN_MARKETPLACE) {
invokeLater {
ShowSettingsUtil.getInstance().showSettingsDialog(
project,
PluginManagerConfigurable::class.java
)
}
}
addJsQuery(JsQuery.INSTALL_PYTHON_PLUGIN) {
invokeLater {
val project = project ?: return@invokeLater
installPlugin(PYTHON_PLUGIN_ID, project)
}
}
addJsQuery(JsQuery.INSTALL_DOCKER_PLUGIN) {
invokeLater {
val project = project ?: return@invokeLater
installPlugin(DOCKER_PLUGIN_ID, project)
}
}
}
private fun installPlugin(idString: String, project: Project) {
installAndEnable(project, setOf(PluginId.getId(idString)), true) {
EditorNotifications.getInstance(project).updateAllNotifications()
}
}
private fun CefEditorModel.addJsQuery(query: JsQuery, handler: (JsQueryRequest) -> Unit) =
addJsQuery(query.queryName, query.request, handler)
private enum class JsQuery(val queryName: String, val request: String) {
OPEN_MARKETPLACE(SUGGESTION_QUERY_NAME, "OPEN_MARKETPLACE"),
INSTALL_PYTHON_PLUGIN(SUGGESTION_QUERY_NAME, "INSTALL_PYTHON_PLUGIN"),
INSTALL_DOCKER_PLUGIN(SUGGESTION_QUERY_NAME, "INSTALL_DOCKER_PLUGIN"),
}
companion object {
private const val TEMPLATE_NAME = "openIdeWelcomeScreen.html"
private const val THEME = "THEME"
private const val SUGGESTION_QUERY_NAME = "suggestionQuery"
private const val PYTHON_PLUGIN_ID = "PythonCore"
private const val DOCKER_PLUGIN_ID = "ru.openide.docker"
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) Haulmont 2024. All Rights Reserved.
* Use is subject to license terms.
*/
package ru.openide.welcome.screen.editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.guessProjectDir
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileSystem
import com.intellij.testFramework.LightVirtualFile
class WelcomeScreenFile(private val parent: VirtualFile? = null)
: LightVirtualFile(NAME, WelcomeScreenFileType.INSTANCE, "") {
override fun getParent() = parent
override fun isWritable() = false
override fun getFileSystem(): VirtualFileSystem = WelcomeScreenFileSystem.getInstance()
companion object {
val NAME = "Welcome"
fun getPath(project: Project) = "${project.guessProjectDir()?.path ?: "."}/$NAME"
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) Haulmont 2024. All Rights Reserved.
* Use is subject to license terms.
*/
package ru.openide.welcome.screen.editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.openapi.vfs.VirtualFileSystem
import com.intellij.openapi.vfs.ex.temp.TempFileSystem
import java.nio.file.Path
class WelcomeScreenFileSystem : TempFileSystem() {
private val files = mutableMapOf<String, WelcomeScreenFile>()
override fun getProtocol() = PROTOCOL
override fun findFileByPath(path: String): WelcomeScreenFile? {
val parentPath = path.substringBeforeLast('/')
val file = files[parentPath]
if (file != null) {
return file
}
return if (path.endsWith(WelcomeScreenFile.NAME)) {
WelcomeScreenFile(VfsUtil.findFile(Path.of(parentPath), false)).also {
files[parentPath] = it
}
}
else {
null
}
}
override fun isWritable(file: VirtualFile) = false
override fun isReadOnly() = true
companion object {
const val PROTOCOL = "openide-welcome-fs"
fun getInstance(): VirtualFileSystem = VirtualFileManager.getInstance().getFileSystem(PROTOCOL)
fun getFile(project: Project) = getInstance().findFileByPath(WelcomeScreenFile.getPath(project))
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (c) Haulmont 2024. All Rights Reserved.
* Use is subject to license terms.
*/
package ru.openide.welcome.screen.editor
import com.intellij.icons.AllIcons
import com.intellij.openapi.fileEditor.FileEditor
import com.intellij.openapi.fileTypes.ex.FakeFileType
import com.intellij.openapi.vfs.VirtualFile
class WelcomeScreenFileType : FakeFileType() {
companion object {
val INSTANCE = WelcomeScreenFileType()
fun isMyFile(file: VirtualFile) = file.fileType is WelcomeScreenFileType
fun isMyEditor(editor: FileEditor) = isMyFile(editor.file)
}
override fun getName() = "WelcomeScreen"
override fun getDescription() = ""
override fun isMyFileType(file: VirtualFile) = isMyFile(file)
override fun getIcon() = AllIcons.Nodes.PpWeb
}

View File

@@ -214,6 +214,7 @@
<virtualFileSystem implementationClass="com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl" key="file" physical="true"/>
<virtualFileSystem implementationClass="com.intellij.openapi.vfs.impl.jar.JarFileSystemImpl" key="jar" physical="true"/>
<virtualFileSystem implementationClass="com.intellij.openapi.vfs.ex.temp.TempFileSystem" key="temp" physical="true"/>
<virtualFileSystem implementationClass="ru.openide.welcome.screen.editor.WelcomeScreenFileSystem" key="openide-welcome-fs" physical="false"/>
<applicationService serviceImplementation="com.intellij.ui.jcef.JBCefStartup" preload="notHeadless" os="mac"/>
<cachesInvalidator implementation="com.intellij.ui.jcef.JBCefAppCacheInvalidator" order="last"/>
@@ -1146,6 +1147,8 @@
<fileEditorProvider id="settingsWindow" implementation="com.intellij.openapi.options.newEditor.settings.SettingsFileEditorProvider"/>
<vcs.fileStatusProvider implementation="com.intellij.openapi.options.newEditor.settings.SettingsFileEditorProvider"/>
<editorTabTitleProvider implementation="com.intellij.openapi.options.newEditor.settings.SettingsFileEditorProvider"/>
<fileEditorProvider id="openide-html-editor" implementation="ru.openide.html.HtmlEditorProvider"/>
<postStartupActivity implementation="ru.openide.welcome.screen.OpenIdeWelcomeScreenProjectActivity" order="last"/>
<editorNotificationProvider implementation="com.intellij.openapi.fileEditor.impl.text.LargeFileNotificationProvider"/>
<fileDocumentSynchronizationVetoer implementation="com.intellij.openapi.fileEditor.impl.LargeFileSavingVetoer"/>

View File

@@ -878,7 +878,7 @@
<separator/>
<group id="LearnGroup"/>
<separator/>
<action id="WhatsNewAction" class="com.intellij.ide.actions.WhatsNewAction"/>
<action id="WhatsNewAction" class="ru.openide.action.OpenIdeWelcomeScreenOpenAction"/>
<action id="MeetNewUIAction" class="com.intellij.ide.ui.experimental.meetNewUi.MeetNewUIAction"/>
<action id="OnlineDocAction" class="com.intellij.ide.actions.OnlineDocAction"/>
<action id="Help.JetBrainsTV" class="com.intellij.ide.actions.JetBrainsTvAction"/>