workbench/mps-platform/source/jetbrains/mps/project/MPSProject.java (144 lines of code) (raw):

/* * Copyright 2003-2025 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jetbrains.mps.project; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.ex.ProjectManagerEx; import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import jetbrains.mps.core.platform.Platform; import jetbrains.mps.extapi.module.SRepositoryRegistry; import jetbrains.mps.ide.MPSCoreComponents; import jetbrains.mps.ide.vfs.IdeaFileSystem; import jetbrains.mps.ide.vfs.ProjectRootListenerComponent; import jetbrains.mps.nodefs.FileSystemProjectBridge; import jetbrains.mps.persistence.PersistenceRegistry; import jetbrains.mps.smodel.MPSModuleRepository; import jetbrains.mps.smodel.WorkbenchModelAccess; import jetbrains.mps.util.annotation.AccessAsPlatformService; import jetbrains.mps.vfs.IFile; import jetbrains.mps.vfs.VFSManager; import jetbrains.mps.vfs.tracking.ConflictResolverImpl; import kotlin.Unit; import org.jdom.JDOMException; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.module.ModelAccess; import org.jetbrains.mps.openapi.module.SRepository; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.concurrent.atomic.AtomicBoolean; /** * Represents a project based on the idea platform project * <p> * fixme introduce a project<->library relation on this particular level (AP) */ public abstract class MPSProject extends ProjectBase implements FileBasedProject, Disposable { private final com.intellij.openapi.project.Project myProject; private final IdeaFileSystem myProjectFileSystem; private FileSystemProjectBridge myFileSystemBridge; // WorkbenchModelAccess is provisional argument. Now it provides implementation of executeCommand method // with respect to shared model lock object from its smodel.ModelAccess superclass. Once each MA has own // model lock object and executeCommand* implementations, we won't need this WMA parameter public MPSProject(@NotNull com.intellij.openapi.project.Project project) { super(project.getName(), MPSCoreComponents.getInstance().getPlatform(), false); myProject = project; myProjectFileSystem = IdeaFileSystem.getInstance(); project.getService(ProjectRootListenerComponent.class).boostProjectRead(myProjectFileSystem); Platform platform = MPSCoreComponents.getInstance().getPlatform(); final MPSModuleRepository extRepo = platform.findComponent(MPSModuleRepository.class); final SRepositoryRegistry registry = platform.findComponent(SRepositoryRegistry.class); final ModelAccess projectMA = WorkbenchModelAccess.getInstance().createForProject(MPSProject.this); final ProjectRepository repo = new ProjectRepository(this, extRepo, registry, projectMA); repo.init(); initRepository(repo); repo.setConflictResolver(new ConflictResolverImpl(this, platform.findComponent(PersistenceRegistry.class), platform.findComponent(VFSManager.class))); } @Override public void projectOpened() { if (myFileSystemBridge == null) { // can't override projectOpened(), go with initComponent() now; have to fix ether of these anyway once get to ProjectComponent here myFileSystemBridge = new FileSystemProjectBridge(this); // FWIW, there's OnReloadingUndoCleaner (at least) that depends on this bridge present for a project myFileSystemBridge.projectOpened(); } super.projectOpened(); } @Override public void projectClosed() { super.projectClosed(); } @Override public void dispose() { super.dispose(); } @Override protected synchronized void destroy() { if (isDisposed()) { return; } myFileSystemBridge.projectClosed(); myFileSystemBridge = null; ((ProjectRepository) getRepository()).setConflictResolver(null); super.destroy(); } @NotNull @Override public File getProjectFile() { String presentableUrl = myProject.getPresentableUrl(); if (presentableUrl == null) { assert myProject.isDefault() : "Broken contract : url is null whenever the project is default!"; throw new IllegalArgumentException("The project url is null (default project?)"); } return new File(presentableUrl); } /** * @return the backing idea project */ @NotNull public com.intellij.openapi.project.Project getProject() { return myProject; } @NotNull @Override public String getName() { // have to keep method here to avoid broken references in mbeddr return super.getName(); } @Override public void save() { getProject().save(); } public static MPSProject open(@NotNull String projectPath) throws InvalidDataException, IOException, JDOMException { com.intellij.openapi.project.Project project = ProjectManagerEx.getInstanceEx().loadAndOpenProject(projectPath); if (project == null) { return null; } // FIXME avoid calling ComponentManger.getComponent return project.getComponent(MPSProject.class); } @Override public <T> T getComponent(Class<T> clazz) { if (isDisposed()) { return null; } T rv; if (clazz.getAnnotation(AccessAsPlatformService.class) != null) { rv = getProject().getService(clazz); } else { //noinspection UnstableApiUsage // FIXME avoid calling ComponentManger.getComponent rv = getProject().getComponent(clazz); } // though would be great to support both components and services, I didn't find a reliable // mechanism to detect whether supplied class is component or a service. Supplied interface may // not be assignable to BaseComponent, only its implementation implements respective component // interface (see EditorExtensionRegistry), and we may end up with getService for a component, // which is not what IDEA tolerates (throws an exception, check // logPluginError call in ComponentManagerImpl.doGetService). if (rv == null) { return super.getComponent(clazz); } return rv; } /** * XXX the method might be worth exposing from {@link FileBasedProject} (with a more generic return type, of course), so that other Project clients has * a chance to access project's FS without need to use global singleton * @return fs one may use to resolve string paths of a project */ public final IdeaFileSystem getFileSystem() { return myProjectFileSystem; } @Override public void reconcileProjectFiles(@Nullable Iterable<IFile> files) { // XXX perhaps, shall pass ProgressMonitor in here? if (files == null) { return; } // original fix was for MPS-14247, refactored now to use IDEA services and to update VCS explicitly ArrayList<VirtualFile> ideaFiles = new ArrayList<>(); for (IFile f : files) { final VirtualFile vf = myProjectFileSystem.asVirtualFile(f); if (vf == null) { continue; } ideaFiles.add(vf); } // want to schedule VCS update *after* IDEA get a chance to find out about new files, hence invokeLater and synchronous refresh // I don't need write nor write intent here (as invokeLater provides), markDirtyAndRefresh is ok with read, just didn't find invokeReadLater(). ApplicationManager.getApplication().invokeLater(() -> { // VfsUtil.markDirtyAndRefresh relies on LocalFileSystem (eventually delegates to RefreshQueue), while our // BaseIdeaFileSystem.refresh uses IDEA's RefreshQueue directly. No idea what's right here. VfsUtil.markDirtyAndRefresh(false, true, true, ideaFiles.toArray(new VirtualFile[0])); // we used to rely on ChangeListManager.scheduleUpdate() of uncertain origin (uses of the method trace back to 1ca3d72f), // but according to Aleksey Pivovarov, it's no-op, and we'd rather stick to VcsDirtyScopeManager // VcsDirtyScopeManager doesn't need read/write or a specific thread, but as long as I want it to run // *after* vfs refresh, I keep it here, inside invokeLater(). XXX Perhaps, have to change invokeLater to some // async job scheduler, just too afraid to do it with 22.2 next door. // FIXME In fact, Aleksey Pivovarov suggests VCS has to pick up VFS changes automatically, perhaps, we could just // use async markDirtyAndRefresh() then? VcsDirtyScopeManager.getInstance(myProject).filesDirty(Collections.emptyList(), ideaFiles); }); } }