2019-07-28 23:53:15 +03:00
// Copyright (c) Ivan Bondarev, Stanislav Mikhalkovich (for details please see \doc\copyright.txt)
2015-06-02 22:52:35 +03:00
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System ;
2025-04-03 11:58:19 +03:00
using System.Collections.Generic ;
2025-04-10 13:14:04 +03:00
using System.Linq ;
2026-03-13 08:02:04 +03:00
using PascalABCCompiler.CoreUtils ;
2015-05-14 22:35:07 +03:00
namespace VisualPascalABC
{
public delegate void ParseInformationUpdatedDelegate ( object obj , string fileName ) ;
2017-05-26 13:29:21 +03:00
public class CodeCompletionParserController : VisualPascalABCPlugins . ICodeCompletionService
2015-05-14 22:35:07 +03:00
{
2025-10-01 11:47:05 +03:00
public static Dictionary < string , bool > filesToParse = new Dictionary < string , bool > ( StringComparer . OrdinalIgnoreCase ) ;
2015-05-14 22:35:07 +03:00
public VisualEnvironmentCompiler visualEnvironmentCompiler ;
private System . Threading . Thread th = null ;
private CodeCompletionProvider ccp ;
public event ParseInformationUpdatedDelegate ParseInformationUpdated ;
public void StopParseThread ( )
{
try
{
if ( th ! = null )
2022-10-19 15:05:42 +03:00
{
2015-05-14 22:35:07 +03:00
th . Abort ( ) ;
2022-10-19 15:05:42 +03:00
th . Join ( ) ; // Это обязательно. По большому счету вообще Abort надо заменить на современное завершение потоков
}
2015-05-14 22:35:07 +03:00
}
catch
{
}
}
public CodeCompletionParserController ( )
{
this . th = new System . Threading . Thread ( new System . Threading . ThreadStart ( ParseInThread ) ) ;
ccp = new CodeCompletionProvider ( ) ;
}
2017-02-19 12:58:42 +03:00
public PascalABCCompiler . Parsers . ICodeCompletionDomConverter GetConverter ( string fileName )
{
return CodeCompletion . CodeCompletionController . comp_modules [ fileName ] as PascalABCCompiler . Parsers . ICodeCompletionDomConverter ;
}
2015-05-14 22:35:07 +03:00
public static string CurrentTwoLetterISO
{
get
{
return CodeCompletion . CodeCompletionController . currentLanguageISO ;
}
set
{
CodeCompletion . CodeCompletionController . currentLanguageISO = value ;
}
}
public void Init ( )
{
2024-05-25 12:26:32 +03:00
//LanguageProvider.Instance.SourceFilesProvider = visualEnvironmentCompiler.SourceFilesProvider;
2015-05-14 22:35:07 +03:00
CodeCompletion . CodeCompletionController . currentLanguageISO = PascalABCCompiler . StringResourcesLanguage . CurrentTwoLetterISO ;
}
public void RenameFile ( string OldFileName , string NewFileName )
{
if ( string . Compare ( OldFileName , NewFileName , true ) ! = 0 )
{
CodeCompletion . CodeCompletionController . comp_modules [ NewFileName ] = CodeCompletion . CodeCompletionController . comp_modules [ OldFileName ] ;
if ( CodeCompletion . CodeCompletionController . comp_modules . ContainsKey ( OldFileName ) )
CodeCompletion . CodeCompletionController . comp_modules . Remove ( OldFileName ) ;
2025-10-01 11:47:05 +03:00
filesToParse [ NewFileName ] = filesToParse [ OldFileName ] ;
if ( filesToParse . ContainsKey ( OldFileName ) )
filesToParse . Remove ( OldFileName ) ;
2015-05-14 22:35:07 +03:00
}
}
public void RegisterFileForParsing ( string FileName )
{
2026-01-27 22:25:28 +03:00
filesToParse [ FileName ] = true ;
2015-05-14 22:35:07 +03:00
}
public void CloseFile ( string FileName )
{
if ( CodeCompletion . CodeCompletionController . comp_modules [ FileName ] ! = null )
CodeCompletion . CodeCompletionController . comp_modules . Remove ( FileName ) ;
2025-10-01 11:47:05 +03:00
filesToParse . Remove ( FileName ) ;
2015-05-14 22:35:07 +03:00
}
public void SetAsChanged ( string FileName )
{
2025-10-01 11:47:05 +03:00
if ( FileName ! = null & & filesToParse . ContainsKey ( FileName ) )
filesToParse [ FileName ] = true ;
2015-05-14 22:35:07 +03:00
}
public void SetAllInProjectChanged ( )
{
try
{
2025-10-01 11:47:05 +03:00
foreach ( string s in filesToParse . Keys . ToArray ( ) )
2015-05-14 22:35:07 +03:00
{
if ( ProjectFactory . Instance . CurrentProject . ContainsSourceFile ( s ) )
2025-10-01 11:47:05 +03:00
filesToParse [ s ] = true ;
2015-05-14 22:35:07 +03:00
}
}
2025-04-03 11:58:19 +03:00
catch ( Exception ) { }
2015-05-14 22:35:07 +03:00
}
2024-05-04 20:21:50 +03:00
/// <summary>
/// Запуск потока с Intellisence
/// </summary>
public void SwitchOnIntellisence ( )
2015-05-14 22:35:07 +03:00
{
th = new System . Threading . Thread ( InternalParsing ) ;
th . Priority = System . Threading . ThreadPriority . BelowNormal ;
th . IsBackground = true ;
th . Start ( ) ;
}
public void StopParsing ( )
{
try
{
VisualPABCSingleton . MainForm . StopTimer ( ) ;
}
catch
{
}
}
private void InternalParsing ( )
{
while ( true )
{
ParseInThread ( ) ;
System . Threading . Thread . Sleep ( 2000 ) ;
}
}
private long mem_delta = 0 ;
internal void ParseInThread ( )
{
try
{
2026-04-03 23:08:57 +03:00
HashSet < string > recomp_files = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ;
2015-05-14 22:35:07 +03:00
bool is_comp = false ;
2025-10-01 11:47:05 +03:00
foreach ( string FileName in filesToParse . Keys . ToArray ( ) ) // копирование ключей обязательно, иначе будет InvalidOperationException EVA
2015-05-14 22:35:07 +03:00
{
2025-04-03 11:58:19 +03:00
2025-10-01 11:47:05 +03:00
if ( filesToParse [ FileName ] )
2015-05-14 22:35:07 +03:00
{
2026-04-03 23:08:57 +03:00
// Попытка компиляции была
filesToParse [ FileName ] = false ;
2015-05-14 22:35:07 +03:00
is_comp = true ;
2026-03-13 08:02:04 +03:00
string text = visualEnvironmentCompiler . SourceFilesProvider ( FileName , SourceFileOperation . GetText ) as string ;
2017-08-31 22:41:26 +03:00
if ( string . IsNullOrEmpty ( text ) )
text = "begin end." ;
2026-04-03 23:08:57 +03:00
if ( CompileWatchedFile ( FileName , text , true ) )
2015-05-14 22:35:07 +03:00
{
2026-04-03 23:08:57 +03:00
// успешная компиляция
recomp_files . Add ( FileName ) ;
2015-05-14 22:35:07 +03:00
}
}
}
2026-04-05 18:00:41 +03:00
if ( recomp_files . Count > 0 )
2015-05-14 22:35:07 +03:00
{
2026-04-05 18:00:41 +03:00
foreach ( string FileName in filesToParse . Keys . ToArray ( ) ) // копирование ключей обязательно, иначе будет InvalidOperationException EVA
2015-05-14 22:35:07 +03:00
{
2026-04-05 18:00:41 +03:00
CodeCompletion . DomConverter dc = CodeCompletion . CodeCompletionController . comp_modules [ FileName ] as CodeCompletion . DomConverter ;
2026-04-03 23:08:57 +03:00
2026-04-05 18:00:41 +03:00
if ( dc ! = null )
{
CodeCompletion . SymScope watchedUnitScope = dc . visitor . entry_scope ;
if ( watchedUnitScope ! = null )
2015-05-14 22:35:07 +03:00
{
2026-04-05 18:00:41 +03:00
var usedUnitsTransitive = watchedUnitScope . GetRealUsedUnitsTransitive ( ) ;
2026-04-03 23:08:57 +03:00
2026-04-05 18:00:41 +03:00
for ( int i = 0 ; i < usedUnitsTransitive . Length ; i + + )
{
string usedUnitFileName = usedUnitsTransitive [ i ] . file_name ;
2026-04-03 23:08:57 +03:00
2026-04-05 18:00:41 +03:00
// Если какая-то из зависимостей была перекомпилирована
if ( recomp_files . Contains ( usedUnitFileName ) )
2015-05-14 22:35:07 +03:00
{
2026-04-05 18:00:41 +03:00
// Помечаем нужные модули для будущей перекомпиляции
InvalidateDependentModules ( usedUnitsTransitive , usedUnitFileName , filesToParse , FileName ) ;
// Перекомпилируем текущий модуль
is_comp = true ;
string text = visualEnvironmentCompiler . SourceFilesProvider ( FileName , SourceFileOperation . GetText ) as string ;
// Здесь третий параметр false, потому что старые данные компиляции еще могут пригодиться до новой перекомпиляции EVA
if ( CompileWatchedFile ( FileName , text , false ) )
{
// успешная компиляция
recomp_files . Add ( FileName ) ;
}
2015-05-14 22:35:07 +03:00
}
}
}
}
}
}
2026-04-05 18:00:41 +03:00
2017-12-30 17:46:38 +03:00
if ( is_comp & & mem_delta > 20000000 /*&& mem_delta > 10000000*/ )
2015-05-14 22:35:07 +03:00
//postavil delta dlja pamjati, posle kototoj delaetsja sborka musora
{
mem_delta = 0 ;
GC . Collect ( ) ;
}
}
2025-04-03 11:58:19 +03:00
catch ( Exception ) { }
2015-05-14 22:35:07 +03:00
}
2026-04-03 23:08:57 +03:00
/// <summary>
/// Вызов компиляции (интеллисенсом) файла с именем fileName и содержимым fileText.
/// Возвращает true, если компиляция была успешна, false в противном случае.
/// </summary>
private bool CompileWatchedFile ( string fileName , string fileText , bool clearOldScope )
{
bool success = false ;
CodeCompletion . DomConverter tmp = CodeCompletion . CodeCompletionController . comp_modules [ fileName ] as CodeCompletion . DomConverter ;
long cur_mem = Environment . WorkingSet ;
CodeCompletion . CodeCompletionController controller = new CodeCompletion . CodeCompletionController ( ) ;
CodeCompletion . DomConverter dc = controller . Compile ( fileName , fileText ) ;
mem_delta + = Environment . WorkingSet - cur_mem ;
if ( dc . is_compiled )
{
if ( clearOldScope & & tmp ! = null )
{
tmp . visitor . entry_scope ? . Clear ( ) ;
tmp . visitor . cur_scope ? . Clear ( ) ;
}
CodeCompletion . CodeCompletionController . comp_modules [ fileName ] = dc ;
success = true ;
ParseInformationUpdated ? . Invoke ( dc . visitor . entry_scope , fileName ) ;
}
else if ( tmp = = null )
CodeCompletion . CodeCompletionController . comp_modules [ fileName ] = dc ;
return success ;
}
/// <summary>
/// Помечает модули из candidateModulesToCheck и watchedFiles для будущей перекомпиляции, если они завивисимы от модуля с именем recompiledDependencyName
/// </summary>
private void InvalidateDependentModules ( CodeCompletion . SymScope [ ] candidateModulesToCheck , string recompiledDependencyName , Dictionary < string , bool > watchedFiles , string currentFileForRecompiling )
{
// Скоупы модулей, соответствующих watchedFiles
var compiledWatchedScopes = watchedFiles . Where ( watchedFile = > watchedFile . Key ! = currentFileForRecompiling )
. Select ( watchedFile = > CodeCompletion . CodeCompletionController . comp_modules [ watchedFile . Key ] )
. SelectMany ( converter = > converter is CodeCompletion . DomConverter dc ?
new CodeCompletion . SymScope [ ] { dc . visitor . entry_scope , dc . visitor . impl_scope }
: Enumerable . Empty < CodeCompletion . SymScope > ( ) )
. Where ( sc = > sc ! = null ) ;
var scopesToCheck = compiledWatchedScopes . Concat ( candidateModulesToCheck ) . Distinct ( ) ;
foreach ( var scope in scopesToCheck )
{
string scopeFileName = scope . file_name ;
if ( scope is CodeCompletion . ImplementationUnitScope )
scopeFileName = ( ( CodeCompletion . SymScope ) scope . TopScope ) . file_name ;
if ( scopeFileName = = recompiledDependencyName )
continue ;
var usedUnitsForUnit = scope . GetRealUsedUnitsTransitive ( ) ;
// Если в зависимостях скоупа есть recompiledDependency
if ( usedUnitsForUnit . FirstOrDefault ( u = > u . file_name = = recompiledDependencyName ) ! = null )
{
var unitOldConverter = CodeCompletion . CodeCompletionController . comp_modules [ scopeFileName ] ;
if ( unitOldConverter ! = null )
{
// Помечаем для перекомпиляции
if ( watchedFiles . ContainsKey ( scopeFileName ) )
{
watchedFiles [ scopeFileName ] = true ;
}
else
{
CodeCompletion . CodeCompletionController . comp_modules . Remove ( scopeFileName ) ;
}
}
}
}
}
2015-05-14 22:35:07 +03:00
public bool IsParsing ( )
{
return th ! = null & & th . ThreadState = = System . Threading . ThreadState . Running ;
}
public void ParseAllFiles ( )
{
if ( visualEnvironmentCompiler . UserOptions . AllowCodeCompletion ) // && visualEnvironmentCompiler.compilerLoaded)
{
if ( th . ThreadState ! = System . Threading . ThreadState . Running )
{
th = new System . Threading . Thread ( new System . Threading . ThreadStart ( this . ParseInThread ) ) ;
th . Priority = System . Threading . ThreadPriority . BelowNormal ;
//th.IsBackground = true;
th . Start ( ) ;
}
//if (th == null)
// RunParseThread();
}
}
}
}