Skip to content

Commit

Permalink
https://github.com/manifold-systems/manifold/issues/467
Browse files Browse the repository at this point in the history
- support error/warning highlights in fragments

Other changes:
- use smart pointers for psifile references in cache
- make open files reparse when dbconfig file changes (see todo)
- other minor fixes
  • Loading branch information
rsmckinney committed Aug 1, 2023
1 parent 4fcf1e9 commit 329766f
Show file tree
Hide file tree
Showing 12 changed files with 353 additions and 82 deletions.
6 changes: 5 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ dependencies {
manifoldAll group: 'systems.manifold', name: 'manifold-all', version: manifoldVersion
manifoldEp group: 'systems.manifold', name: 'manifold-ext-producer-sample', version: manifoldVersion

// this is for manifold-sql where use of hikari brings in slf4j
// if we don't add the nop here, hikari's exception logging looks like unhandled exceptions that IJ reports as manifold errors
//implementation 'org.slf4j:slf4j-api:2.07'

implementation project('jps-plugin')
}

Expand Down Expand Up @@ -122,7 +126,7 @@ runIde {
minHeapSize = '1g'
maxHeapSize = '4g'
// uncomment to override the ide that is run
// ideDir = new File("C:\\Program Files\\JetBrains\\IntelliJ IDEA Community Edition 2023.1.1")
// ideDir = new File("C:\\Program Files\\JetBrains\\IntelliJ IDEA 2023.1.2")
}

patchPluginXml {
Expand Down
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
#
#

version=2023.1.10
manifoldVersion=2023.1.10-SNAPSHOT
version=2023.1.11
manifoldVersion=2023.1.11-SNAPSHOT
org.gradle.jvmargs=-Dfile.encoding=UTF-8
defaultIjVersion=LATEST-EAP-SNAPSHOT
37 changes: 35 additions & 2 deletions src/main/java/manifold/ij/core/ManModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import manifold.internal.host.SimpleModule;
import manifold.strings.StringLiteralTemplateProcessor;
import manifold.util.NecessaryEvilUtil;
import manifold.util.ReflectUtil;
import manifold.util.concurrent.LocklessLazyVar;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.model.java.compiler.JpsJavaCompilerOptions;
Expand Down Expand Up @@ -325,8 +326,40 @@ private void initializeModuleClassLoader()

URL[] urls = classpath.stream().map( dir -> dir.toURI().toURL() ).toArray( URL[]::new );

// note this classloader is used exclusively for finding a loading type manifold services
_typeManifoldClassLoader = new URLClassLoader( urls, getClass().getClassLoader() );
// note this classloader is used exclusively for finding and loading type manifold services
_typeManifoldClassLoader =
new URLClassLoader( urls, getClass().getClassLoader() )
{
/**
* Total hack to avoid Jar-hell with IJ's PathClassLoader, a parent loader in the chain. For example, if a project
* uses manifold-sql with H2, those jars must load in this URLClassLoader so manifold-sql can do its thing. However,
* since IJ apparently uses part of H2 internally, its PathClassLoader, being a parent loader of this loader, will
* load H2 classes, which are probably not from the same version of H2, etc. Therefore, overloading loadClass()
* here for non-manifold classes ensures that ij's loader won't interfere with dependencies of manifold classes.
*/
@Override
protected Class<?> loadClass( String name, boolean resolve ) throws ClassNotFoundException
{
ClassLoader parent = null;
try
{
if( !name.startsWith( "manifold." ) ) // if( name.startsWith( "org.h2." ) )
{
// jump over PathClassLoader to the great-grandfather since none of these classes should have a dependency on IJ classes
parent = (ClassLoader)ReflectUtil.field( this, "parent" ).get();
ReflectUtil.field( this, "parent" ).set( ClassLoader.getPlatformClassLoader() );
}
return super.loadClass( name, resolve );
}
finally
{
if( !name.startsWith( "manifold." ) )
{
ReflectUtil.field( this, "parent" ).set( parent );
}
}
}
};
}

@Override
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/manifold/ij/core/ManProject.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.intellij.openapi.compiler.CompilerPaths;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.fileEditor.*;
import com.intellij.openapi.fileEditor.ex.FileEditorWithProvider;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.module.ModuleUtil;
Expand All @@ -44,7 +45,6 @@
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.psi.PsiElement;
Expand Down Expand Up @@ -650,7 +650,7 @@ private int findJdkVersion()
private void addBuildPropertiesFilePersistenceListener()
{
_permanentProjectConnection.subscribe( AppTopics.FILE_DOCUMENT_SYNC,
new BuildPropertiesFilePersistenceListener( _ijProject ) );
new ReparseFileTrigger( _ijProject ) );
}

private void addModuleClasspathListener()
Expand All @@ -667,11 +667,11 @@ private void addModuleClasspathListener()
*/
private void addFileOpenedListener()
{
_permanentProjectConnection.subscribe( FileEditorManagerListener.FILE_EDITOR_MANAGER,
new FileEditorManagerListener()
_permanentProjectConnection.subscribe( FileOpenedSyncListener.TOPIC,
new FileOpenedSyncListener()
{
@Override
public void fileOpenedSync( @NotNull FileEditorManager source, @NotNull VirtualFile file, @NotNull Pair<FileEditor[], FileEditorProvider[]> editors )
public void fileOpenedSync( @NotNull FileEditorManager source, @NotNull VirtualFile file, @NotNull List<FileEditorWithProvider> editors )
{
if( !isManifoldInUse() )
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,44 +32,49 @@
import org.jetbrains.annotations.NotNull;

/**
* For preprocessor. When a build.properties files is saved, open Java files reparse.
* For preprocessor and dbconfig. When a build.properties or *.dbconfig file is saved, open Java files reparse.
*
* todo: make the conditions for reparsing, currently dbconfig and build.properties, pluggable.
* todo: even better: if manifold IModel had concept of model dependencies, we could determine exactly the set of files
* that need to reparse instead of reparsing everything. We could also reparse non-open files, because we need to if the
* file was opened then closed because IJ does not retokenize when reopening a file.
*/
class BuildPropertiesFilePersistenceListener implements FileDocumentManagerListener
class ReparseFileTrigger implements FileDocumentManagerListener
{
private Project _ijProject;
private final Project _ijProject;

BuildPropertiesFilePersistenceListener( Project ijProject )
ReparseFileTrigger( Project ijProject )
{
_ijProject = ijProject;
}

@Override
public void beforeDocumentSaving( @NotNull Document document )
{
reparseFiles( document );
maybeReparseOpenJavaFiles( document );
}

@Override
public void fileContentReloaded( @NotNull VirtualFile file, @NotNull Document document )
{
reparseFiles( document );
maybeReparseOpenJavaFiles( document );
}

@Override
public void fileContentLoaded( @NotNull VirtualFile file, @NotNull Document document )
{
reparseFiles( document );
maybeReparseOpenJavaFiles( document );
}

private void reparseFiles( @NotNull Document document )
private void maybeReparseOpenJavaFiles( @NotNull Document document )
{
if( isBuildProperties( document ) )
if( shouldReparse( document ) )
{
ReparseUtil.reparseOpenJavaFiles( _ijProject );
}
}

private boolean isBuildProperties( Document document )
private boolean shouldReparse( Document document )
{
VirtualFile vfile = FileDocumentManager.getInstance().getFile( document );
if( vfile == null || vfile instanceof LightVirtualFile )
Expand All @@ -85,7 +90,17 @@ private boolean isBuildProperties( Document document )
PsiFile psiFile = PsiDocumentManager.getInstance( _ijProject ).getPsiFile( document );
if( psiFile != null )
{
return Definitions.BUILD_PROPERTIES.equalsIgnoreCase( vfile.getName() );
String fileExt = vfile.getExtension();
if( fileExt != null && fileExt.equalsIgnoreCase( "dbconfig" ) )
{
// DbConfig file changed
return true;
}
else if( Definitions.BUILD_PROPERTIES.equalsIgnoreCase( vfile.getName() ) )
{
// Build.properties file changed
return true;
}
}
}
catch( Throwable ignore )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ public PsiType getType()
if( value != null )
{
String fqn = getFragmentValueFqn( value );
if( fqn == null )
{
return null;
}
return JavaPsiFacade.getInstance( getProject() ).getParserFacade().createTypeFromText( fqn, this );
}
}
Expand Down Expand Up @@ -127,7 +131,7 @@ private String getFragmentClassName( IFileFragment fragment )
}
else
{
SmartPsiElementPointer container = (SmartPsiElementPointer)fragment.getContainer();
MaybeSmartPsiElementPointer container = (MaybeSmartPsiElementPointer)fragment.getContainer();
if( container != null )
{
PsiElement elem = container.getElement();
Expand All @@ -146,7 +150,9 @@ private String getFragmentValueFqn( PsiAnnotationMemberValue value )
String fqn = null;
if( value instanceof PsiReferenceExpression )
{
fqn = ((PsiField)((PsiReferenceExpression)value).resolve()).computeConstantValue().toString();
PsiField resolvedField = (PsiField)((PsiReferenceExpression)value).resolve();
Object resolvedRef = resolvedField == null ? null : resolvedField.computeConstantValue();
fqn = resolvedRef == null ? null : resolvedRef.toString();
}
else if( value instanceof PsiLiteralExpression )
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,19 @@
import com.intellij.lang.Language;
import com.intellij.lang.LanguageUtil;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.fileTypes.ex.FileTypeIdentifiableByVirtualFile;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.InjectedLanguagePlaces;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.LanguageInjector;
import com.intellij.psi.PsiLanguageInjectionHost;
import com.intellij.psi.impl.source.tree.java.PsiLiteralExpressionImpl;
import com.intellij.psi.injection.ReferenceInjector;
import com.intellij.psi.tree.IElementType;
import com.intellij.testFramework.LightVirtualFile;
import manifold.ij.core.ManProject;
import manifold.internal.javac.FragmentProcessor;
import manifold.internal.javac.HostKind;
Expand Down Expand Up @@ -68,7 +73,56 @@ public void getLanguagesToInject( @NotNull PsiLanguageInjectionHost host, @NotNu

private Language getLanguageFromExt( String ext )
{
return LanguageUtil.getFileTypeLanguage( FileTypeManager.getInstance().getFileTypeByExtension( ext ) );
ReferenceInjector injector = ReferenceInjector.findById( ext );
if( injector != null )
{
return injector.toLanguage();
}

Language fileTypeLanguage = LanguageUtil.getFileTypeLanguage( FileTypeManager.getInstance().getFileTypeByExtension( ext ) );
if( fileTypeLanguage != null )
{
return fileTypeLanguage;
}

LightVirtualFile virtualFileNamedAsLanguageId = new LightVirtualFile( ext );
LightVirtualFile virtualFileWithLanguageIdAsExtension = new LightVirtualFile( "textmate." + ext );
for( FileType fileType : FileTypeManager.getInstance().getRegisteredFileTypes() )
{
if( fileType instanceof LanguageFileType &&
fileType instanceof FileTypeIdentifiableByVirtualFile fileTypeByVf )
{
if( fileTypeByVf.isMyFileType( virtualFileNamedAsLanguageId ) ||
fileTypeByVf.isMyFileType( virtualFileWithLanguageIdAsExtension ) )
{
return ((LanguageFileType)fileType).getLanguage();
}
}
}

return null;
}

public static Language getLanguageByString(@NotNull String languageId) {
ReferenceInjector injector = ReferenceInjector.findById(languageId);
if (injector != null) return injector.toLanguage();
FileTypeManager fileTypeManager = FileTypeManager.getInstance();
FileType fileType = fileTypeManager.getFileTypeByExtension(languageId);
if (fileType instanceof LanguageFileType) {
return ((LanguageFileType)fileType).getLanguage();
}

LightVirtualFile virtualFileNamedAsLanguageId = new LightVirtualFile(languageId);
LightVirtualFile virtualFileWithLanguageIdAsExtension = new LightVirtualFile("textmate." + languageId);
for (FileType registeredFileType : fileTypeManager.getRegisteredFileTypes()) {
if (registeredFileType instanceof FileTypeIdentifiableByVirtualFile &&
registeredFileType instanceof LanguageFileType &&
(((FileTypeIdentifiableByVirtualFile)registeredFileType).isMyFileType(virtualFileNamedAsLanguageId) ||
((FileTypeIdentifiableByVirtualFile)registeredFileType).isMyFileType(virtualFileWithLanguageIdAsExtension))) {
return ((LanguageFileType)registeredFileType).getLanguage();
}
}
return null;
}

private HostKind getStringKind( @NotNull PsiLanguageInjectionHost host )
Expand Down
Loading

0 comments on commit 329766f

Please sign in to comment.