From 312ea1ad22ca45c669ccc471e4ab736623665ab1 Mon Sep 17 00:00:00 2001 From: fishca Date: Thu, 30 Apr 2026 12:19:13 +0300 Subject: [PATCH 1/4] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=BE:=20=D1=80=D0=B0=D1=81=D0=BA=D1=80?= =?UTF-8?q?=D0=B0=D1=81=D0=BA=D0=B0=20=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=B2?= =?UTF-8?q?=D1=8B=D1=85=20=D1=81=D0=BB=D0=BE=D0=B2,=20=D1=81=D0=B2=D0=BE?= =?UTF-8?q?=D1=80=D0=B0=D1=87=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D1=86=D0=B5=D0=B4=D1=83=D1=80,=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=80=D0=B8=D0=B5=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CommonForms.cpp | 20 +- src/CommonModules.cpp | 456 +++++++++++++++++---- src/MainUnit.cpp | 762 +++++++++++++++++++++++++++++++++++- src/MainUnit.dfm | 3 +- src/MainUnit.h | 38 ++ src/MetadataTreeBuilder.cpp | 11 + src/SynHighlighter1C.cpp | 362 +++++++++++++++++ src/SynHighlighter1C.h | 92 +++++ 8 files changed, 1645 insertions(+), 99 deletions(-) create mode 100644 src/SynHighlighter1C.cpp create mode 100644 src/SynHighlighter1C.h diff --git a/src/CommonForms.cpp b/src/CommonForms.cpp index 6c0b824..8669c92 100644 --- a/src/CommonForms.cpp +++ b/src/CommonForms.cpp @@ -34,6 +34,22 @@ namespace return L""; } + + String __fastcall GetManagedFormModuleText(tree* root) + { + if (!root || root->get_num_subnode() <= 0) + return L""; + + tree* formRoot = root->get_subnode(0); + if (!formRoot || formRoot->get_num_subnode() <= 2) + return L""; + + tree* moduleNode = formRoot->get_subnode(2); + if (!moduleNode || moduleNode->get_type() != nd_string) + return L""; + + return moduleNode->get_value(); + } } __fastcall TCommonForms::TCommonForms() : BaseMetadataObject() @@ -124,7 +140,9 @@ void __fastcall TCommonForms::LoadTextIfNeeded() if (text.IsEmpty()) { std::unique_ptr form_tree(get_treeFromV8file(data_form)); - text = FindEmbeddedModuleText(form_tree.get()); + text = GetManagedFormModuleText(form_tree.get()); + if (text.IsEmpty()) + text = FindEmbeddedModuleText(form_tree.get()); } } diff --git a/src/CommonModules.cpp b/src/CommonModules.cpp index 5cca76d..30b01ce 100644 --- a/src/CommonModules.cpp +++ b/src/CommonModules.cpp @@ -2,10 +2,357 @@ #pragma hdrstop +#include + #include "CommonModules.h" //--------------------------------------------------------------------------- #pragma package(smart_init) +namespace +{ + bool __fastcall LooksLikeUtf16Le(const TBytes& bytes) + { + int limit = bytes.Length < 200 ? bytes.Length : 200; + int checked = 0; + int zeroOdd = 0; + + for (int i = 1; i < limit; i += 2) + { + ++checked; + if (bytes[i] == 0) + ++zeroOdd; + } + + return checked > 0 && zeroOdd * 2 >= checked; + } + + String __fastcall DecodeModuleText(const TBytes& sourceBytes, int sourceSize) + { + if (sourceSize <= 0 || sourceBytes.empty()) + return L""; + + TBytes bytes = sourceBytes; + sourceSize = sourceSize < bytes.Length ? sourceSize : bytes.Length; + + TEncoding *enc = nullptr; + int off = TEncoding::GetBufferEncoding(bytes, enc); + if (off > 0) + { + TBytes unicodeBytes = TEncoding::Convert(enc, TEncoding::Unicode, bytes, off, sourceSize - off); + if (!unicodeBytes.empty()) + return String((wchar_t*)&unicodeBytes[0], unicodeBytes.Length / 2); + } + + if (LooksLikeUtf16Le(bytes)) + return String((wchar_t*)&bytes[0], sourceSize / 2); + + try + { + return TEncoding::UTF8->GetString(bytes, 0, sourceSize); + } + catch (...) + { + return String((char*)&bytes[0], sourceSize); + } + } + + bool __fastcall LooksLike1CModuleText(const String& value) + { + return value.Length() > 20 + && (value.Pos(L"\n") > 0 + || value.Pos(L"\r") > 0 + || value.Pos(L"Процедура") > 0 + || value.Pos(L"Функция") > 0 + || value.Pos(L"КонецПроцедуры") > 0 + || value.Pos(L"КонецФункции") > 0); + } + + bool __fastcall IsGuidLikeChar(wchar_t ch) + { + return (ch >= L'0' && ch <= L'9') + || (ch >= L'a' && ch <= L'f') + || (ch >= L'A' && ch <= L'F'); + } + + bool __fastcall IsGuidLike(const String& value) + { + if (value.Length() != 36) + return false; + + for (int i = 1; i <= value.Length(); i++) + { + wchar_t ch = value[i]; + if (i == 9 || i == 14 || i == 19 || i == 24) + { + if (ch != L'-') + return false; + } + else if (!IsGuidLikeChar(ch)) + return false; + } + + return true; + } + + void __fastcall AddUniqueString(std::vector& values, const String& value) + { + if (value.IsEmpty()) + return; + + String upperValue = value.UpperCase(); + for (const auto& existing : values) + if (existing.UpperCase() == upperValue) + return; + + values.push_back(value); + } + + void __fastcall AddModuleFileCandidates(std::vector& candidates, const String& baseGuid) + { + AddUniqueString(candidates, baseGuid + L".0"); + AddUniqueString(candidates, baseGuid + L".1"); + AddUniqueString(candidates, baseGuid + L".2"); + AddUniqueString(candidates, baseGuid + L".3"); + AddUniqueString(candidates, baseGuid); + } + + String __fastcall NormalizeGuidFileName(const String& guid) + { + String result = Trim(guid).LowerCase(); + if (result.Length() >= 2 && result[1] == L'{' && result[result.Length()] == L'}') + result = result.SubString(2, result.Length() - 2); + return result; + } + + void __fastcall CollectGuidReferences(tree* node, std::vector& guids) + { + if (!node) + return; + + if (IsGuidLike(node->get_value())) + AddUniqueString(guids, node->get_value()); + + for (int i = 0; i < node->get_num_subnode(); i++) + CollectGuidReferences(node->get_subnode(i), guids); + } + + String __fastcall FindEmbeddedModuleText(tree* node) + { + if (!node) + return L""; + + if (node->get_type() == nd_string && LooksLike1CModuleText(node->get_value())) + return node->get_value(); + + for (int i = 0; i < node->get_num_subnode(); i++) + { + String found = FindEmbeddedModuleText(node->get_subnode(i)); + if (!found.IsEmpty()) + return found; + } + + return L""; + } + + String __fastcall ReadV8FileAsText(v8file* file) + { + if (!file) + return L""; + + TBytes bytes; + TBytesStream* sb = new TBytesStream(bytes); + try + { + file->SaveToStream(sb); + String text = DecodeModuleText(sb->Bytes, sb->Size); + delete sb; + return text; + } + catch (...) + { + delete sb; + return L""; + } + } + + String __fastcall ReadDiskFileAsText(const String& fileName) + { + if (!FileExists(fileName)) + return L""; + + TFileStream* fs = nullptr; + TBytesStream* sb = nullptr; + try + { + fs = new TFileStream(fileName, fmOpenRead | fmShareDenyNone); + TBytes bytes; + sb = new TBytesStream(bytes); + sb->CopyFrom(fs, 0); + String text = DecodeModuleText(sb->Bytes, sb->Size); + delete sb; + delete fs; + return text; + } + catch (...) + { + delete sb; + delete fs; + return L""; + } + } + + void __fastcall AddUniquePath(std::vector& values, const String& value) + { + if (value.IsEmpty()) + return; + + String normalized = ExcludeTrailingPathDelimiter(value).UpperCase(); + for (const auto& existing : values) + if (ExcludeTrailingPathDelimiter(existing).UpperCase() == normalized) + return; + + values.push_back(value); + } + + String __fastcall TryReadUnpackedModuleText(const String& guid) + { + std::vector sourceDirs; + AddUniquePath(sourceDirs, TPath::Combine(GetCurrentDir(), L"SourceCF")); + AddUniquePath(sourceDirs, TPath::Combine(ExtractFilePath(ParamStr(0)), L"SourceCF")); + String normalizedGuid = NormalizeGuidFileName(guid); + + for (const auto& sourceDir : sourceDirs) + { + if (!TDirectory::Exists(sourceDir)) + continue; + + for (int i = 0; i <= 5; ++i) + { + const String objectDir = TPath::Combine(sourceDir, normalizedGuid + L"." + IntToStr(i)); + const String textFileName = TPath::Combine(objectDir, L"text"); + String text = ReadDiskFileAsText(textFileName); + if (!text.IsEmpty() && LooksLike1CModuleText(text)) + return text; + + const String moduleFileName = TPath::Combine(objectDir, L"module"); + text = ReadDiskFileAsText(moduleFileName); + if (!text.IsEmpty() && LooksLike1CModuleText(text)) + return text; + + const String directFileName = TPath::Combine(sourceDir, normalizedGuid + L"." + IntToStr(i)); + text = ReadDiskFileAsText(directFileName); + if (!text.IsEmpty() && LooksLike1CModuleText(text)) + return text; + } + } + + return L""; + } + + String __fastcall TryReadUnpackedModuleTextByName(const String& moduleName) + { + if (moduleName.IsEmpty()) + return L""; + + std::vector sourceDirs; + AddUniquePath(sourceDirs, TPath::Combine(GetCurrentDir(), L"SourceCF")); + AddUniquePath(sourceDirs, TPath::Combine(ExtractFilePath(ParamStr(0)), L"SourceCF")); + + for (const auto& sourceDir : sourceDirs) + { + if (!TDirectory::Exists(sourceDir)) + continue; + + TStringDynArray files = TDirectory::GetFiles(sourceDir); + for (int i = 0; i < files.Length; ++i) + { + const String fileName = files[i]; + String objectGuid = ExtractFileName(fileName); + if (!IsGuidLike(objectGuid)) + continue; + + String metadataText = ReadDiskFileAsText(fileName); + if (metadataText.Pos(moduleName) <= 0) + continue; + + String moduleText = TryReadUnpackedModuleText(objectGuid); + if (!moduleText.IsEmpty()) + return moduleText; + } + } + + return L""; + } + + String __fastcall TryReadNamedTextFile(v8catalog* catalog, const String& fileName) + { + if (!catalog) + return L""; + + v8file* file = catalog->GetFile(fileName); + if (!file) + return L""; + + String text = ReadV8FileAsText(file); + return LooksLike1CModuleText(text) ? text : L""; + } + + String __fastcall TryReadModuleContainer(v8file* file) + { + if (!file) + return L""; + + try + { + v8catalog* catalog = new v8catalog(file); + if (catalog) + { + catalog->ClearIs8316(); + + String text = TryReadNamedTextFile(catalog, L"text"); + if (text.IsEmpty()) + text = TryReadNamedTextFile(catalog, L"module"); + + if (text.IsEmpty()) + { + for (v8file* child = catalog->GetFirst(); child; child = child->GetNext()) + { + String childName = child->GetFileName().LowerCase(); + if (childName.Pos(L"text") > 0 || childName.Pos(L"module") > 0) + { + text = ReadV8FileAsText(child); + if (LooksLike1CModuleText(text)) + break; + text = L""; + } + } + } + + delete catalog; + if (!text.IsEmpty()) + return text; + } + } + catch (...) + { + } + + String directText = ReadV8FileAsText(file); + if (LooksLike1CModuleText(directText)) + return directText; + + try + { + std::unique_ptr objectTree(get_treeFromV8file(file)); + return FindEmbeddedModuleText(objectTree.get()); + } + catch (...) + { + return L""; + } + } +} + __fastcall TCommonModules::TCommonModules() : BaseMetadataObject() { @@ -34,96 +381,46 @@ void __fastcall TCommonModules::LoadTextIfNeeded() if (textLoaded) return; - TBytesStream* sb = nullptr; - TEncoding *enc = nullptr; - TBytes bytes, sb_bytes; - int off = 0; text = L""; + if (!guid.IsEmpty()) + text = TryReadUnpackedModuleText(guid); + if (text.IsEmpty()) + text = TryReadUnpackedModuleTextByName(name); + if ((parent) && (!guid.IsEmpty())) { - v8file* data_module = parent->GetFile(guid+".0"); - if (data_module) + std::vector candidates; + AddModuleFileCandidates(candidates, guid); + + try { - v8catalog* data_module_cat = new v8catalog(data_module); - if (data_module_cat) + v8file* objectFile = parent->GetFile(guid); + if (objectFile) { - auto text_module = data_module_cat->GetFile("text"); - auto image_module = data_module_cat->GetFile("image"); - if (text_module) - { - data_module_cat->ClearIs8316(); + std::unique_ptr objectTree(get_treeFromV8file(objectFile)); + if (text.IsEmpty()) + text = FindEmbeddedModuleText(objectTree.get()); - sb = new TBytesStream(bytes); - text_module->SaveToStream(sb); - enc = nullptr; - sb_bytes = sb->Bytes; - if (!sb_bytes.empty()) - { - off = TEncoding::GetBufferEncoding(sb_bytes, enc); - if(off == 0) - { - delete sb; - sb = nullptr; - } - - if (sb) - { - bytes = TEncoding::Convert(enc, TEncoding::Unicode, sb_bytes, off, sb->Size-off); - if (!bytes.empty()) - { - String text_data = String((wchar_t*)&bytes[0], bytes.Length / 2); - text = text_data; - } - else - { - String text_data = ""; - text = text_data; - } - } - else - { - String text_data = ""; - text = text_data; - } - } - else - { - text = ""; - } - delete sb; - sb = nullptr; - } - else if (image_module) - { - text = "Исходный текст модуля отсутствует"; - } - else + std::vector referencedGuids; + CollectGuidReferences(objectTree.get(), referencedGuids); + for (const auto& referencedGuid : referencedGuids) { - try - { -// data_module->Open(); -// TBytesStream* sb = new TBytesStream(bytes); -// data_module->SaveToStream(sb); -// enc = NULL; -// off = TEncoding::GetBufferEncoding(sb->Bytes, enc); -// if(off == 0) -// { -// delete sb; -// } -// -// bytes = TEncoding::Convert(enc, TEncoding::Unicode, sb->Bytes, off, sb->Size-off); -// String text_data = String((wchar_t*)&bytes[0], bytes.Length / 2); -// -// text = text_data; - - text = "Не удалось получить текст модуля"; - } catch (...) - { - } + if (text.IsEmpty()) + text = TryReadUnpackedModuleText(referencedGuid); + AddModuleFileCandidates(candidates, referencedGuid); } } } + catch (...) + { + } + + for (size_t i = 0; i < candidates.size() && text.IsEmpty(); i++) + { + v8file* data_module = parent->GetFile(candidates[i]); + text = TryReadModuleContainer(data_module); + } } textLoaded = true; @@ -143,6 +440,7 @@ String __fastcall TCommonModules::GetText() void __fastcall TCommonModules::SetText(String _text) { text = _text; + textLoaded = true; } void __fastcall TCommonModules::initializeFromTree() diff --git a/src/MainUnit.cpp b/src/MainUnit.cpp index d14835d..f0d254d 100644 --- a/src/MainUnit.cpp +++ b/src/MainUnit.cpp @@ -3,6 +3,7 @@ #include #pragma hdrstop +#include #include #include @@ -75,14 +76,24 @@ #pragma link "SynMemo" #pragma link "SynEditHighlighter" #pragma link "SynHighlighterCpp" +#pragma link "SynHighlighterGeneral" #pragma link "SynEdit" #pragma link "SynEditHighlighter" #pragma link "SynHighlighterCpp" +#pragma link "SynHighlighterGeneral" #pragma link "SynMemo" #pragma resource "*.dfm" TMainForm *MainForm; MessageRegistrator* msreg; +static const TColor DefaultHighlightKeywordColor = clNavy; +static const TColor DefaultHighlightCommentColor = clGreen; +static const TColor DefaultHighlightStringColor = clMaroon; +static const TColor DefaultHighlightNumberColor = clPurple; +static const TColor DefaultHighlightPreprocessorColor = clTeal; +static const TColor DefaultHighlightSymbolColor = clGrayText; +static const TColor DefaultHighlightAnnotationColor = clOlive; + static bool IsVerboseUiLoggingEnabled() { String envValue = GetEnvironmentVariable(L"V8READER_VERBOSE_LOG"); @@ -137,6 +148,401 @@ static bool IsFileLoggingEnabled() return false; } +static String GetHighlightSettingsFileName() +{ + String appData = GetEnvironmentVariable(L"APPDATA"); + if (appData.IsEmpty()) + appData = TPath::GetHomePath(); + + String settingsDir = TPath::Combine(appData, L"v8reader"); + ForceDirectories(settingsDir); + return TPath::Combine(settingsDir, L"highlight.ini"); +} + +static void Configure1CHighlighter(TSyn1CSyn* highlighter) +{ + if (!highlighter) + return; + + highlighter->CommentAttri->Foreground = DefaultHighlightCommentColor; + highlighter->IdentifierAttri->Foreground = clWindowText; + highlighter->SpaceAttri->Foreground = clWindowText; + highlighter->KeyAttri->Foreground = DefaultHighlightKeywordColor; + highlighter->NumberAttri->Foreground = DefaultHighlightNumberColor; + highlighter->DirectiveAttri->Foreground = DefaultHighlightPreprocessorColor; + highlighter->StringAttri->Foreground = DefaultHighlightStringColor; + highlighter->SymbolAttri->Foreground = DefaultHighlightSymbolColor; + highlighter->AnnotationAttri->Foreground = DefaultHighlightAnnotationColor; + highlighter->KeyAttri->Style = TFontStyles() << fsBold; + highlighter->CommentAttri->Style = TFontStyles() << fsItalic; +} + +static void ConfigureModuleGeneralHighlighter(TSynGeneralSyn* highlighter) +{ + if (!highlighter) + return; + + highlighter->Comments = TCommentStyles() << csCPPStyle; + highlighter->StringDelim = sdDoubleQuote; + highlighter->DetectPreprocessor = true; + highlighter->IdentifierChars = + L"_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + L"абвгдеёжзийклмнопрстуфхцчшщъыьэюя" + L"АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ"; + + highlighter->KeyWords->Clear(); + const wchar_t* keywords[] = { + L"Если", L"Тогда", L"Иначе", L"ИначеЕсли", L"КонецЕсли", + L"Для", L"Каждого", L"Из", L"По", L"Цикл", L"Пока", L"КонецЦикла", + L"Процедура", L"КонецПроцедуры", L"Функция", L"КонецФункции", + L"Возврат", L"Экспорт", L"Попытка", L"Исключение", L"КонецПопытки", + L"ВызватьИсключение", L"Продолжить", L"Прервать", L"Перейти", + L"Новый", L"Неопределено", L"Истина", L"Ложь", L"И", L"Или", L"Не", + L"Выполнить", L"Перем" + }; + + for (int i = 0; i < sizeof(keywords) / sizeof(keywords[0]); ++i) + { + highlighter->KeyWords->Add(keywords[i]); + highlighter->KeyWords->Add(String(keywords[i]).LowerCase()); + } + + highlighter->CommentAttri->Foreground = DefaultHighlightCommentColor; + highlighter->CommentAttri->Style = TFontStyles() << fsItalic; + highlighter->IdentifierAttri->Foreground = clBlack; + highlighter->KeyAttri->Foreground = DefaultHighlightKeywordColor; + highlighter->KeyAttri->Style = TFontStyles() << fsBold; + highlighter->NumberAttri->Foreground = DefaultHighlightNumberColor; + highlighter->PreprocessorAttri->Foreground = DefaultHighlightPreprocessorColor; + highlighter->StringAttri->Foreground = DefaultHighlightStringColor; + highlighter->SymbolAttri->Foreground = DefaultHighlightSymbolColor; + highlighter->SpaceAttri->Foreground = clBlack; +} + +static TColorBox* CreateHighlightColorBox(TWinControl* parent, int top, TColor selected) +{ + TColorBox* colorBox = new TColorBox(parent); + colorBox->Parent = parent; + colorBox->Left = 210; + colorBox->Top = top - 3; + colorBox->Width = 240; + colorBox->Height = 22; + colorBox->Style = TColorBoxStyle() << cbStandardColors << cbExtendedColors << cbSystemColors << cbPrettyNames; + colorBox->Selected = selected; + return colorBox; +} + +static String Strip1CStringAndComment(const String& line) +{ + String result; + bool inString = false; + + for (int i = 1; i <= line.Length(); ++i) + { + wchar_t ch = line[i]; + if (inString) + { + if (ch == L'"') + { + if (i < line.Length() && line[i + 1] == L'"') + ++i; + else + inString = false; + } + result += L' '; + continue; + } + + if (ch == L'"') + { + inString = true; + result += L' '; + continue; + } + + if (ch == L'/' && i < line.Length() && line[i + 1] == L'/') + break; + + result += ch; + } + + return Trim(result).LowerCase(); +} + +static bool IsLineComment(const UnicodeString& s) +{ + UnicodeString t = s.TrimLeft(); + return t.SubString(1, 2) == L"//"; +} + + +static bool Is1CProcedureOrFunctionStart(const String& line) +{ + return line.Pos(L"\u043F\u0440\u043E\u0446\u0435\u0434\u0443\u0440\u0430") == 1 + || line.Pos(L"\u0444\u0443\u043D\u043A\u0446\u0438\u044F") == 1; +} + +static bool Is1CProcedureOrFunctionEnd(const String& line) +{ + return line.Pos(L"\u043A\u043E\u043D\u0435\u0446\u043F\u0440\u043E\u0446\u0435\u0434\u0443\u0440\u044B") == 1 + || line.Pos(L"\u043A\u043E\u043D\u0435\u0446\u0444\u0443\u043D\u043A\u0446\u0438\u0438") == 1; +} + +static PVirtualNode GetActiveTreeNode(TVirtualStringTree* tree, PVirtualNode fallbackNode = nullptr) +{ + if (fallbackNode) + return fallbackNode; + + if (!tree) + return nullptr; + + if (tree->FocusedNode) + return tree->FocusedNode; + + return tree->GetFirstSelected(); +} + +static bool LooksLikeGuidFileName(const String& value) +{ + if (value.Length() != 36) + return false; + + for (int i = 1; i <= value.Length(); ++i) + { + wchar_t ch = value[i]; + if (i == 9 || i == 14 || i == 19 || i == 24) + { + if (ch != L'-') + return false; + } + else if (!((ch >= L'0' && ch <= L'9') + || (ch >= L'a' && ch <= L'f') + || (ch >= L'A' && ch <= L'F'))) + return false; + } + + return true; +} + +static String DecodeTextFileForModuleFallback(const String& fileName) +{ + if (!FileExists(fileName)) + return L""; + + try + { + TBytes bytes = TFile::ReadAllBytes(fileName); + if (bytes.empty()) + return L""; + + TEncoding* encoding = nullptr; + int offset = TEncoding::GetBufferEncoding(bytes, encoding); + if (offset > 0) + return encoding->GetString(bytes, offset, bytes.Length - offset); + + int limit = bytes.Length < 200 ? bytes.Length : 200; + int checked = 0; + int zeroOdd = 0; + for (int i = 1; i < limit; i += 2) + { + ++checked; + if (bytes[i] == 0) + ++zeroOdd; + } + + if (checked > 0 && zeroOdd * 2 >= checked) + return String((wchar_t*)&bytes[0], bytes.Length / 2); + + return TEncoding::UTF8->GetString(bytes); + } + catch (...) + { + return L""; + } +} + +static bool LooksLike1CModuleFallbackText(const String& text) +{ + return text.Length() > 20 + && (text.Pos(L"\n") > 0 + || text.Pos(L"\r") > 0 + || text.Pos(L"Процедура") > 0 + || text.Pos(L"Функция") > 0 + || text.Pos(L"КонецПроцедуры") > 0 + || text.Pos(L"КонецФункции") > 0); +} + +static String ReadUnpackedModuleTextByObjectGuid(const String& sourceDir, const String& guid) +{ + for (int i = 0; i <= 5; ++i) + { + String suffix = L"." + IntToStr(i); + String objectDir = TPath::Combine(sourceDir, guid + suffix); + + String text = DecodeTextFileForModuleFallback(TPath::Combine(objectDir, L"text")); + if (LooksLike1CModuleFallbackText(text)) + return text; + + text = DecodeTextFileForModuleFallback(TPath::Combine(objectDir, L"module")); + if (LooksLike1CModuleFallbackText(text)) + return text; + + text = DecodeTextFileForModuleFallback(TPath::Combine(sourceDir, guid + suffix)); + if (LooksLike1CModuleFallbackText(text)) + return text; + } + + return L""; +} + +static String FindUnpackedModuleTextByNodeName(const String& nodeName) +{ + if (nodeName.IsEmpty()) + return L""; + + std::vector sourceDirs; + sourceDirs.push_back(TPath::Combine(GetCurrentDir(), L"SourceCF")); + sourceDirs.push_back(TPath::Combine(ExtractFilePath(ParamStr(0)), L"SourceCF")); + + for (const auto& sourceDir : sourceDirs) + { + if (!TDirectory::Exists(sourceDir)) + continue; + + TStringDynArray files = TDirectory::GetFiles(sourceDir); + for (int i = 0; i < files.Length; ++i) + { + String guid = ExtractFileName(files[i]); + if (!LooksLikeGuidFileName(guid)) + continue; + + String metadataText = DecodeTextFileForModuleFallback(files[i]); + if (metadataText.Pos(nodeName) <= 0) + continue; + + String moduleText = ReadUnpackedModuleTextByObjectGuid(sourceDir, guid); + if (!moduleText.IsEmpty()) + return moduleText; + } + } + + return L""; +} + +void __fastcall TMainForm::ModuleMemoScanForFoldRanges(TObject *Sender, + TSynFoldRanges *FoldRanges, TStrings *LinesToScan, int FromLine, int ToLine) +{ + if (FoldRanges == NULL || LinesToScan == NULL) + return; + + const int ProcedureFoldType = 2; + const int CommentFoldType = 1; + + bool inCommentBlock = false; + int commentStartLine = -1; + + bool inMultiLineComment = false; + int multiCommentStartLine = -1; + + bool inProcBlock = false; + + for (int lineIndex = FromLine; lineIndex <= ToLine; ++lineIndex) + { + if (lineIndex < 0 || lineIndex >= LinesToScan->Count) + continue; + + int foldLine = lineIndex + 1; + String originalLine = LinesToScan->Strings[lineIndex]; + String line = Strip1CStringAndComment(originalLine); + + bool foldInfoSet = false; + bool isLineComment = IsLineComment(originalLine); + bool startsMultiLineComment = !isLineComment && line.Pos(L"/*") > 0; + bool endsMultiLineComment = line.Pos(L"*/") > 0; + + // --- folding для многострочных комментариев /* */ --- + if (!inMultiLineComment && startsMultiLineComment) + { + if (endsMultiLineComment) + { + FoldRanges->NoFoldInfo(foldLine); + } + else + { + inMultiLineComment = true; + multiCommentStartLine = foldLine; + FoldRanges->StartFoldRange(multiCommentStartLine, CommentFoldType); + } + foldInfoSet = true; + } + else if (inMultiLineComment && endsMultiLineComment) + { + FoldRanges->StopFoldRange(foldLine, CommentFoldType); + inMultiLineComment = false; + multiCommentStartLine = -1; + foldInfoSet = true; + } + else if (inMultiLineComment) + { + FoldRanges->NoFoldInfo(foldLine); + foldInfoSet = true; + } + + // --- folding для последовательных // комментариев --- + if (!foldInfoSet) + { + if (isLineComment) + { + if (!inCommentBlock) + { + inCommentBlock = true; + commentStartLine = foldLine; + FoldRanges->StartFoldRange(commentStartLine, CommentFoldType); + } + else + { + FoldRanges->NoFoldInfo(foldLine); + } + foldInfoSet = true; + } + else if (inCommentBlock) + { + FoldRanges->StopFoldRange(foldLine - 1, CommentFoldType); + inCommentBlock = false; + commentStartLine = -1; + } + } + + // --- folding для procedure/function --- + if (!foldInfoSet) + { + if (line.IsEmpty()) + { + FoldRanges->NoFoldInfo(foldLine); + } + else if (Is1CProcedureOrFunctionStart(line)) + { + inProcBlock = true; + FoldRanges->StartFoldRange(foldLine, ProcedureFoldType); + } + else if (inProcBlock && Is1CProcedureOrFunctionEnd(line)) + { + FoldRanges->StopFoldRange(foldLine, ProcedureFoldType); + inProcBlock = false; + } + else + { + FoldRanges->NoFoldInfo(foldLine); + } + } + } + + if (inCommentBlock) + FoldRanges->StopFoldRange(ToLine + 1, CommentFoldType); + if (inMultiLineComment) + FoldRanges->StopFoldRange(ToLine + 1, CommentFoldType); +} + static void AddConditionalInfoMessage(Messager* mess, const String& message) { if (mess && mess->getUiMessagesEnabled() && IsVerboseUiLoggingEnabled()) @@ -164,11 +570,7 @@ static String FormatHeapStatus() + L", Uncommitted=" + IntToStr((__int64)hs.TotalUncommitted); } -static void LogHeapStatus(const String& stage, - const String& guid = L"", - const String& fileName = L"", - int currentIndex = -1, - int totalCount = -1) +static void LogHeapStatus(const String& stage, const String& guid = L"", const String& fileName = L"", int currentIndex = -1, int totalCount = -1) { Messager* activeMessager = dynamic_cast(msreg); if(!msreg || (activeMessager && !activeMessager->getUiMessagesEnabled())) return; @@ -186,12 +588,234 @@ static void LogHeapStatus(const String& stage, msreg->AddMessage(stage, msInfo, ts); } +void __fastcall TMainForm::SetDefaultHighlightSettingsControls() +{ + if (HighlightKeywordColorBox) HighlightKeywordColorBox->Selected = DefaultHighlightKeywordColor; + if (HighlightCommentColorBox) HighlightCommentColorBox->Selected = DefaultHighlightCommentColor; + if (HighlightStringColorBox) HighlightStringColorBox->Selected = DefaultHighlightStringColor; + if (HighlightNumberColorBox) HighlightNumberColorBox->Selected = DefaultHighlightNumberColor; + if (HighlightPreprocessorColorBox) HighlightPreprocessorColorBox->Selected = DefaultHighlightPreprocessorColor; + if (HighlightSymbolColorBox) HighlightSymbolColorBox->Selected = DefaultHighlightSymbolColor; + if (HighlightAnnotationColorBox) HighlightAnnotationColorBox->Selected = DefaultHighlightAnnotationColor; + if (HighlightKeywordBoldCheckBox) HighlightKeywordBoldCheckBox->Checked = true; + if (HighlightCommentItalicCheckBox) HighlightCommentItalicCheckBox->Checked = true; +} + +void __fastcall TMainForm::ApplyHighlightSettings() +{ + if (!Syn1CSyn && !ModuleGeneralSyn) + return; + + if (Syn1CSyn) + { + if (HighlightKeywordColorBox) Syn1CSyn->KeyAttri->Foreground = HighlightKeywordColorBox->Selected; + if (HighlightCommentColorBox) Syn1CSyn->CommentAttri->Foreground = HighlightCommentColorBox->Selected; + if (HighlightStringColorBox) Syn1CSyn->StringAttri->Foreground = HighlightStringColorBox->Selected; + if (HighlightNumberColorBox) Syn1CSyn->NumberAttri->Foreground = HighlightNumberColorBox->Selected; + if (HighlightPreprocessorColorBox) Syn1CSyn->DirectiveAttri->Foreground = HighlightPreprocessorColorBox->Selected; + if (HighlightSymbolColorBox) Syn1CSyn->SymbolAttri->Foreground = HighlightSymbolColorBox->Selected; + if (HighlightAnnotationColorBox) Syn1CSyn->AnnotationAttri->Foreground = HighlightAnnotationColorBox->Selected; + + Syn1CSyn->KeyAttri->Style = + (HighlightKeywordBoldCheckBox && HighlightKeywordBoldCheckBox->Checked) ? (TFontStyles() << fsBold) : TFontStyles(); + Syn1CSyn->CommentAttri->Style = + (HighlightCommentItalicCheckBox && HighlightCommentItalicCheckBox->Checked) ? (TFontStyles() << fsItalic) : TFontStyles(); + } + + if (ModuleGeneralSyn) + { + if (HighlightKeywordColorBox) ModuleGeneralSyn->KeyAttri->Foreground = HighlightKeywordColorBox->Selected; + if (HighlightCommentColorBox) ModuleGeneralSyn->CommentAttri->Foreground = HighlightCommentColorBox->Selected; + if (HighlightStringColorBox) ModuleGeneralSyn->StringAttri->Foreground = HighlightStringColorBox->Selected; + if (HighlightNumberColorBox) ModuleGeneralSyn->NumberAttri->Foreground = HighlightNumberColorBox->Selected; + if (HighlightPreprocessorColorBox) ModuleGeneralSyn->PreprocessorAttri->Foreground = HighlightPreprocessorColorBox->Selected; + if (HighlightSymbolColorBox) ModuleGeneralSyn->SymbolAttri->Foreground = HighlightSymbolColorBox->Selected; + ModuleGeneralSyn->IdentifierAttri->Foreground = clBlack; + ModuleGeneralSyn->SpaceAttri->Foreground = clBlack; + ModuleGeneralSyn->KeyAttri->Style = + (HighlightKeywordBoldCheckBox && HighlightKeywordBoldCheckBox->Checked) ? (TFontStyles() << fsBold) : TFontStyles(); + ModuleGeneralSyn->CommentAttri->Style = + (HighlightCommentItalicCheckBox && HighlightCommentItalicCheckBox->Checked) ? (TFontStyles() << fsItalic) : TFontStyles(); + } + if (MemoObject) MemoObject->Invalidate(); + if (MemoManager) MemoManager->Invalidate(); + if (HighlightPreviewMemo) HighlightPreviewMemo->Invalidate(); +} + +void __fastcall TMainForm::LoadHighlightSettings() +{ + HighlightSettingsLoading = true; + std::unique_ptr ini(new TIniFile(GetHighlightSettingsFileName())); + HighlightKeywordColorBox->Selected = (TColor)ini->ReadInteger(L"Colors", L"Keyword", DefaultHighlightKeywordColor); + HighlightCommentColorBox->Selected = (TColor)ini->ReadInteger(L"Colors", L"Comment", DefaultHighlightCommentColor); + HighlightStringColorBox->Selected = (TColor)ini->ReadInteger(L"Colors", L"String", DefaultHighlightStringColor); + HighlightNumberColorBox->Selected = (TColor)ini->ReadInteger(L"Colors", L"Number", DefaultHighlightNumberColor); + HighlightPreprocessorColorBox->Selected = (TColor)ini->ReadInteger(L"Colors", L"Preprocessor", DefaultHighlightPreprocessorColor); + HighlightSymbolColorBox->Selected = (TColor)ini->ReadInteger(L"Colors", L"Symbol", DefaultHighlightSymbolColor); + HighlightAnnotationColorBox->Selected = (TColor)ini->ReadInteger(L"Colors", L"Annotation", DefaultHighlightAnnotationColor); + HighlightKeywordBoldCheckBox->Checked = ini->ReadBool(L"Style", L"KeywordBold", true); + HighlightCommentItalicCheckBox->Checked = ini->ReadBool(L"Style", L"CommentItalic", true); + HighlightSettingsLoading = false; + + ApplyHighlightSettings(); +} + +void __fastcall TMainForm::SaveHighlightSettings() +{ + std::unique_ptr ini(new TIniFile(GetHighlightSettingsFileName())); + ini->WriteInteger(L"Colors", L"Keyword", HighlightKeywordColorBox->Selected); + ini->WriteInteger(L"Colors", L"Comment", HighlightCommentColorBox->Selected); + ini->WriteInteger(L"Colors", L"String", HighlightStringColorBox->Selected); + ini->WriteInteger(L"Colors", L"Number", HighlightNumberColorBox->Selected); + ini->WriteInteger(L"Colors", L"Preprocessor", HighlightPreprocessorColorBox->Selected); + ini->WriteInteger(L"Colors", L"Symbol", HighlightSymbolColorBox->Selected); + ini->WriteInteger(L"Colors", L"Annotation", HighlightAnnotationColorBox->Selected); + ini->WriteBool(L"Style", L"KeywordBold", HighlightKeywordBoldCheckBox->Checked); + ini->WriteBool(L"Style", L"CommentItalic", HighlightCommentItalicCheckBox->Checked); +} + +void __fastcall TMainForm::HighlightSettingsChanged(TObject *Sender) +{ + if (HighlightSettingsLoading) + return; + + ApplyHighlightSettings(); + SaveHighlightSettings(); +} + +void __fastcall TMainForm::ResetHighlightSettingsClick(TObject *Sender) +{ + HighlightSettingsLoading = true; + SetDefaultHighlightSettingsControls(); + HighlightSettingsLoading = false; + ApplyHighlightSettings(); + SaveHighlightSettings(); +} + +void __fastcall TMainForm::CreateHighlightSettingsTab() +{ + TTabSheet* previousActivePage = pagesEdit ? pagesEdit->ActivePage : nullptr; + HighlightSettingsTab = new TTabSheet(this); + HighlightSettingsTab->PageControl = pagesEdit; + HighlightSettingsTab->Caption = L"Подсветка"; + + TPanel* panel = new TPanel(HighlightSettingsTab); + panel->Parent = HighlightSettingsTab; + panel->Align = alClient; + panel->BevelOuter = bvNone; + panel->ParentColor = true; + + const wchar_t* captions[] = { + L"Ключевые слова", L"Комментарии", L"Строки", + L"Числа", L"Директивы #", L"Символы", L"Аннотации &" + }; + TColorBox** boxes[] = { + &HighlightKeywordColorBox, &HighlightCommentColorBox, &HighlightStringColorBox, + &HighlightNumberColorBox, &HighlightPreprocessorColorBox, &HighlightSymbolColorBox, + &HighlightAnnotationColorBox + }; + TColor defaults[] = { + DefaultHighlightKeywordColor, DefaultHighlightCommentColor, DefaultHighlightStringColor, + DefaultHighlightNumberColor, DefaultHighlightPreprocessorColor, DefaultHighlightSymbolColor, + DefaultHighlightAnnotationColor + }; + + for (int i = 0; i < 7; ++i) + { + int top = 18 + i * 34; + TLabel* label = new TLabel(panel); + label->Parent = panel; + label->Left = 18; + label->Top = top; + label->Caption = captions[i]; + + *boxes[i] = CreateHighlightColorBox(panel, top, defaults[i]); + (*boxes[i])->OnChange = HighlightSettingsChanged; + } + + HighlightKeywordBoldCheckBox = new TCheckBox(panel); + HighlightKeywordBoldCheckBox->Parent = panel; + HighlightKeywordBoldCheckBox->Left = 18; + HighlightKeywordBoldCheckBox->Top = 264; + HighlightKeywordBoldCheckBox->Width = 250; + HighlightKeywordBoldCheckBox->Caption = L"Ключевые слова жирным"; + HighlightKeywordBoldCheckBox->Checked = true; + HighlightKeywordBoldCheckBox->OnClick = HighlightSettingsChanged; + + HighlightCommentItalicCheckBox = new TCheckBox(panel); + HighlightCommentItalicCheckBox->Parent = panel; + HighlightCommentItalicCheckBox->Left = 18; + HighlightCommentItalicCheckBox->Top = 294; + HighlightCommentItalicCheckBox->Width = 250; + HighlightCommentItalicCheckBox->Caption = L"Комментарии курсивом"; + HighlightCommentItalicCheckBox->Checked = true; + HighlightCommentItalicCheckBox->OnClick = HighlightSettingsChanged; + + TButton* resetButton = new TButton(panel); + resetButton->Parent = panel; + resetButton->Left = 18; + resetButton->Top = 336; + resetButton->Width = 150; + resetButton->Caption = L"Сбросить"; + resetButton->OnClick = ResetHighlightSettingsClick; + + HighlightPreviewMemo = new TSynMemo(panel); + HighlightPreviewMemo->Parent = panel; + HighlightPreviewMemo->Left = 480; + HighlightPreviewMemo->Top = 18; + HighlightPreviewMemo->Width = 430; + HighlightPreviewMemo->Height = 230; + HighlightPreviewMemo->Anchors = TAnchors() << akLeft << akTop << akRight; + HighlightPreviewMemo->ReadOnly = true; + HighlightPreviewMemo->Font->Name = L"Courier New"; + HighlightPreviewMemo->Font->Size = 10; + HighlightPreviewMemo->Highlighter = Syn1CSyn; + HighlightPreviewMemo->OnScanForFoldRanges = ModuleMemoScanForFoldRanges; + HighlightPreviewMemo->UseCodeFolding = true; + HighlightPreviewMemo->Lines->Text = + L"&НаСервере\n" + L"Процедура ОбновитьДанные() Экспорт\n" + L" // Комментарий\n" + L" Если Значение = 10 Тогда\n" + L" Сообщить(\"Готово\");\n" + L" КонецЕсли;\n" + L"КонецПроцедуры"; + HighlightPreviewMemo->Invalidate(); + + LoadHighlightSettings(); + if (previousActivePage) + pagesEdit->ActivePage = previousActivePage; +} //--------------------------------------------------------------------------- -__fastcall TMainForm::TMainForm(TComponent* Owner) : TForm(Owner), MDManager(std::make_unique()) +__fastcall TMainForm::TMainForm(TComponent* Owner) : TForm(Owner), HighlightSettingsLoading(false), + ModuleGeneralSyn(nullptr), ModuleSelectionTimer(nullptr), LastModuleNodeShown(nullptr), MDManager(std::make_unique()) { VirtualStringTreeValue1C->NodeDataSize = sizeof(VirtualTreeData); + Syn1CSyn = new TSyn1CSyn(this); + Configure1CHighlighter(Syn1CSyn); + ModuleGeneralSyn = new TSynGeneralSyn(this); + ConfigureModuleGeneralHighlighter(ModuleGeneralSyn); + MemoObject->Highlighter = Syn1CSyn; + MemoManager->Highlighter = Syn1CSyn; + MemoObject->Color = clWindow; + MemoManager->Color = clWindow; + MemoObject->Font->Color = clBlack; + MemoManager->Font->Color = clBlack; + MemoObject->OnScanForFoldRanges = ModuleMemoScanForFoldRanges; + MemoManager->OnScanForFoldRanges = ModuleMemoScanForFoldRanges; + MemoObject->UseCodeFolding = true; + MemoManager->UseCodeFolding = true; + VirtualStringTreeValue1C->OnClick = VirtualStringTreeValue1CClick; + VirtualStringTreeValue1C->OnChange = VirtualStringTreeValue1CChange; + VirtualStringTreeValue1C->OnNodeClick = VirtualStringTreeValue1CNodeClick; + VirtualStringTreeValue1C->OnFocusChanged = VirtualStringTreeValue1CFocusChanged; + ModuleSelectionTimer = new TTimer(this); + ModuleSelectionTimer->Interval = 250; + ModuleSelectionTimer->OnTimer = ModuleSelectionTimerTimer; + ModuleSelectionTimer->Enabled = true; + CreateHighlightSettingsTab(); mess = new Messager(ListViewMessager, StatusBar1); LoadProgressBar->Position = 0; LoadProgressBar->Visible = false; @@ -351,7 +975,7 @@ static const std::unordered_set catalogTypes = {md_Catalogs, md_Document if (CurCat) ::fillJournalTree(VirtualStringTreeValue1C, childNode, childData, imgIndex, CurCat); } } -else if (chartAccTypes.count(md_name)) + else if (chartAccTypes.count(md_name)) { if (md_name == md_ChartOfAccounts) { @@ -490,6 +1114,7 @@ void __fastcall TMainForm::FillVirtualTree() { // Создаем корневой узел VirtualStringTreeValue1C->Clear(); + LastModuleNodeShown = nullptr; PVirtualNode RootNode = VirtualStringTreeValue1C->AddChild(nullptr); @@ -544,6 +1169,18 @@ void __fastcall TMainForm::FillVirtualTree() { childDataCom->text_module = L""; childDataCom->MetadataObject = mdObj; + TCommonModules* commonModule = dynamic_cast(mdObj); + if (commonModule) + { + childDataCom->text_module = commonModule->GetText(); + } + else + { + TCommonForms* commonForm = dynamic_cast(mdObj); + if (commonForm) + childDataCom->text_module = commonForm->GetText(); + } + if (categoryCom.name == md_ExchangePlans || categoryCom.name == md_FilterCriteria) { BaseMetadataObject* metadataObject = dynamic_cast(mdObj); @@ -802,6 +1439,12 @@ void __fastcall TMainForm::ActionFileOpenExecute(TObject *Sender) AddConditionalInfoMessage(mess, L"ActionFileOpenExecute: построение дерева интерфейса"); AdvanceLoadProgress(L"Построение дерева метаданных..."); + std::vector filter; + const String sourceCfDir = TPath::Combine(ExtractFilePath(ParamStr(0)), L"SourceCF"); + if (TDirectory::Exists(sourceCfDir)) + TDirectory::Delete(sourceCfDir, true); + v8unpack::Parse(AnsiString(EditNameCF->Text).c_str(), AnsiString(sourceCfDir).c_str(), filter); + VirtualStringTreeValue1C->BeginUpdate(); try { @@ -2404,28 +3047,111 @@ void __fastcall TMainForm::FormDestroy(TObject *Sender) } //--------------------------------------------------------------------------- -void __fastcall TMainForm::VirtualStringTreeValue1CClick(TObject *Sender) +void __fastcall TMainForm::ShowMetadataNodeText(PVirtualNode Node) { - PVirtualNode Node = VirtualStringTreeValue1C->FocusedNode; + if (!MemoObject) + return; - VirtualTreeData* Data = (VirtualTreeData*)VirtualStringTreeValue1C->GetNodeData(Node); + Node = GetActiveTreeNode(VirtualStringTreeValue1C, Node); + if (!Node) + return; - MemoObject->Clear(); + VirtualTreeData* Data = (VirtualTreeData*)VirtualStringTreeValue1C->GetNodeData(Node); + bool moduleTextSelected = false; + String moduleText = L""; + LastModuleNodeShown = Node; if (Data) { - TCommonModules* module = dynamic_cast(Data->MetadataObject); - if (module) - MemoObject->Text = module->GetText(); + if (!Data->text_module.IsEmpty()) + { + moduleText = Data->text_module; + moduleTextSelected = true; + } else { - TCommonForms* commonForm = dynamic_cast(Data->MetadataObject); - if (commonForm) - MemoObject->Text = commonForm->GetText(); + TCommonModules* module = dynamic_cast(Data->MetadataObject); + if (module) + { + moduleText = module->GetText(); + moduleTextSelected = true; + } else - MemoObject->Text = Data->text_module; + { + TCommonForms* commonForm = dynamic_cast(Data->MetadataObject); + if (commonForm) + { + moduleText = commonForm->GetText(); + moduleTextSelected = true; + } + } + } + + if (moduleText.IsEmpty()) + { + moduleText = FindUnpackedModuleTextByNodeName(Data->Name); + moduleTextSelected = !moduleText.IsEmpty(); } } + + MemoObject->BeginUpdate(); + try + { + MemoObject->Lines->Text = moduleText; + } + __finally + { + MemoObject->EndUpdate(); + } + MemoObject->CaretX = 1; + MemoObject->CaretY = 1; + MemoObject->TopLine = 1; + MemoObject->LeftChar = 1; + MemoObject->Invalidate(); + MemoObject->Refresh(); + + if (moduleTextSelected && mess) + { + String nodeName = Data ? Data->Name : L""; + mess->Status(L"Модуль: " + nodeName + L", длина текста: " + IntToStr(moduleText.Length())); + } + + if (moduleTextSelected && pagesEdit && TabModuleObject) + pagesEdit->ActivePage = TabModuleObject; +} + +void __fastcall TMainForm::VirtualStringTreeValue1CClick(TObject *Sender) +{ + ShowMetadataNodeText(GetActiveTreeNode(VirtualStringTreeValue1C)); +} +//--------------------------------------------------------------------------- + +void __fastcall TMainForm::VirtualStringTreeValue1CChange(TBaseVirtualTree *Sender, PVirtualNode Node) +{ + ShowMetadataNodeText(Node); +} +//--------------------------------------------------------------------------- + +void __fastcall TMainForm::VirtualStringTreeValue1CNodeClick(TBaseVirtualTree *Sender, const THitInfo &HitInfo) +{ + ShowMetadataNodeText(HitInfo.HitNode); +} +//--------------------------------------------------------------------------- + +void __fastcall TMainForm::ModuleSelectionTimerTimer(TObject *Sender) +{ + PVirtualNode node = GetActiveTreeNode(VirtualStringTreeValue1C); + if (!node || node == LastModuleNodeShown) + return; + + ShowMetadataNodeText(node); +} +//--------------------------------------------------------------------------- + +void __fastcall TMainForm::VirtualStringTreeValue1CFocusChanged(TBaseVirtualTree *Sender, PVirtualNode Node, + TColumnIndex Column) +{ + ShowMetadataNodeText(Node); } //--------------------------------------------------------------------------- diff --git a/src/MainUnit.dfm b/src/MainUnit.dfm index 3894f55..7173d37 100644 --- a/src/MainUnit.dfm +++ b/src/MainUnit.dfm @@ -114,6 +114,7 @@ object MainForm: TMainForm TreeOptions.PaintOptions = [toShowButtons, toShowDropmark, toShowTreeLines, toShowVertGridLines, toThemeAware, toUseBlendedImages] TreeOptions.SelectionOptions = [toExtendedFocus, toFullRowSelect] OnClick = VirtualStringTreeValue1CClick + OnFocusChanged = VirtualStringTreeValue1CFocusChanged OnFreeNode = VirtualStringTreeValue1CFreeNode OnGetText = VirtualStringTreeValue1CGetText OnGetImageIndex = VirtualStringTreeValue1CGetImageIndex @@ -137,7 +138,7 @@ object MainForm: TMainForm Top = 1 Width = 571 Height = 507 - ActivePage = TabSheet1 + ActivePage = TabModuleObject Align = alClient Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText diff --git a/src/MainUnit.h b/src/MainUnit.h index 9a518f9..20ff6dc 100644 --- a/src/MainUnit.h +++ b/src/MainUnit.h @@ -19,10 +19,14 @@ #include #include #include +#include #include "SynEdit.hpp" #include "SynEditHighlighter.hpp" #include "SynHighlighterCpp.hpp" +#include "SynEditCodeFolding.hpp" +#include "SynHighlighterGeneral.hpp" #include "SynMemo.hpp" +#include "SynHighlighter1C.h" #include @@ -44,6 +48,9 @@ #include "SynMemo.hpp" #include "SynEditHighlighter.hpp" #include "SynHighlighterCpp.hpp" +#include "SynEditCodeFolding.hpp" +#include "SynHighlighterGeneral.hpp" +#include "SynHighlighter1C.h" class Messager; @@ -109,11 +116,42 @@ class TMainForm : public TForm void __fastcall ActionOpenCFExecute(TObject *Sender); void __fastcall FormDestroy(TObject *Sender); void __fastcall VirtualStringTreeValue1CClick(TObject *Sender); + void __fastcall VirtualStringTreeValue1CChange(TBaseVirtualTree *Sender, PVirtualNode Node); + void __fastcall VirtualStringTreeValue1CNodeClick(TBaseVirtualTree *Sender, const THitInfo &HitInfo); + void __fastcall VirtualStringTreeValue1CFocusChanged(TBaseVirtualTree *Sender, PVirtualNode Node, + TColumnIndex Column); + void __fastcall ModuleMemoScanForFoldRanges(TObject *Sender, TSynFoldRanges *FoldRanges, + TStrings *LinesToScan, int FromLine, int ToLine); + void __fastcall ModuleSelectionTimerTimer(TObject *Sender); private: // User declarations Messager* mess; // регистратор сообщений + TSyn1CSyn *Syn1CSyn; + TSynGeneralSyn *ModuleGeneralSyn; + TTabSheet *HighlightSettingsTab; + TColorBox *HighlightKeywordColorBox; + TColorBox *HighlightCommentColorBox; + TColorBox *HighlightStringColorBox; + TColorBox *HighlightNumberColorBox; + TColorBox *HighlightPreprocessorColorBox; + TColorBox *HighlightSymbolColorBox; + TColorBox *HighlightAnnotationColorBox; + TCheckBox *HighlightKeywordBoldCheckBox; + TCheckBox *HighlightCommentItalicCheckBox; + TSynMemo *HighlightPreviewMemo; + bool HighlightSettingsLoading; + TTimer *ModuleSelectionTimer; + PVirtualNode LastModuleNodeShown; std::unique_ptr MDManager; // Умный указатель для автоматического управления памятью + void __fastcall CreateHighlightSettingsTab(); + void __fastcall ApplyHighlightSettings(); + void __fastcall LoadHighlightSettings(); + void __fastcall SaveHighlightSettings(); + void __fastcall SetDefaultHighlightSettingsControls(); + void __fastcall HighlightSettingsChanged(TObject *Sender); + void __fastcall ResetHighlightSettingsClick(TObject *Sender); + void __fastcall ShowMetadataNodeText(PVirtualNode Node); public: // User declarations __fastcall TMainForm(TComponent* Owner); void __fastcall ResetLoadProgress(int maxValue, const String& statusText = L""); diff --git a/src/MetadataTreeBuilder.cpp b/src/MetadataTreeBuilder.cpp index a419034..2167354 100644 --- a/src/MetadataTreeBuilder.cpp +++ b/src/MetadataTreeBuilder.cpp @@ -14,6 +14,8 @@ void initNode(VirtualTreeData* data, const String& name, int imageIndex, int age data->Name = name; data->Age = age; data->ImgIndex = imageIndex; + data->text_module = L""; + data->MetadataObject = nullptr; } PVirtualNode addChildNode(TVirtualStringTree* tree, PVirtualNode parent, const String& name, int imageIndex, int age) @@ -91,6 +93,7 @@ void fillCatalogsTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualT if (!metadataObject) return; initNode(childData, metadataObject->name, imgIndex); + childData->MetadataObject = metadataObject; fillStandardMetadataSections(tree, childNode, metadataObject); } @@ -100,6 +103,7 @@ void fillFormsCommandsTree(TVirtualStringTree* tree, PVirtualNode childNode, Vir if (!metadataObject) return; initNode(childData, metadataObject->name, imgIndex); + childData->MetadataObject = metadataObject; addSection(tree, childNode, L"Формы", TreeImage::Forms, TreeImage::Forms, metadataObject->getForms(), [](const auto& item) { return item->name; }); addSection(tree, childNode, L"Команды", TreeImage::Commands, TreeImage::Commands, @@ -112,6 +116,7 @@ void fillAccumulationRegisterTree(TVirtualStringTree* tree, PVirtualNode childNo if (!metadataObject) return; initNode(childData, metadataObject->name, imgIndex); + childData->MetadataObject = metadataObject; fillInformationRegisterSections(tree, childNode, metadataObject); } @@ -121,6 +126,7 @@ void fillCalculationRegisterTree(TVirtualStringTree* tree, PVirtualNode childNod if (!metadataObject) return; initNode(childData, metadataObject->name, imgIndex); + childData->MetadataObject = metadataObject; fillInformationRegisterSections(tree, childNode, metadataObject); } @@ -130,6 +136,7 @@ void fillInformationRegisterTree(TVirtualStringTree* tree, PVirtualNode childNod if (!metadataObject) return; initNode(childData, metadataObject->name, imgIndex); + childData->MetadataObject = metadataObject; fillInformationRegisterSections(tree, childNode, metadataObject); } @@ -139,6 +146,7 @@ void fillAccountingRegisterTree(TVirtualStringTree* tree, PVirtualNode childNode if (!metadataObject) return; initNode(childData, metadataObject->name, imgIndex); + childData->MetadataObject = metadataObject; addSection(tree, childNode, L"Измерения", TreeImage::Dimensions, TreeImage::Dimensions, metadataObject->getDimensions(), [](const auto& item) { return item->name; }); @@ -164,6 +172,7 @@ void fillChartAccTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualT if (!metadataObject) return; initNode(childData, metadataObject->name, imgIndex); + childData->MetadataObject = metadataObject; addSection(tree, childNode, L"Реквизиты", TreeImage::Attributes, TreeImage::Attributes, metadataObject->getAttributes(), [](const auto& item) { return item->name; }); @@ -186,6 +195,7 @@ void fillJournalTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTr if (!metadataObject) return; initNode(childData, metadataObject->name, imgIndex); + childData->MetadataObject = metadataObject; addSection(tree, childNode, L"Графы", TreeImage::JournalColumns, TreeImage::JournalColumns, metadataObject->getAttributes(), [](const auto& item) { return item->name; }); @@ -202,6 +212,7 @@ void fillEnumTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeD int imgIndex, TEnums* curEnum) { initNode(childData, curEnum->name, imgIndex); + childData->MetadataObject = curEnum; addSection(tree, childNode, L"Значения", TreeImage::Attributes, TreeImage::Attributes, curEnum->attributes, [](const auto& item) { return item; }); diff --git a/src/SynHighlighter1C.cpp b/src/SynHighlighter1C.cpp new file mode 100644 index 0000000..be517c0 --- /dev/null +++ b/src/SynHighlighter1C.cpp @@ -0,0 +1,362 @@ +//--------------------------------------------------------------------------- + +#include +#pragma hdrstop + +#include "SynHighlighter1C.h" + +//--------------------------------------------------------------------------- + +__fastcall TSyn1CSyn::TSyn1CSyn(TComponent* AOwner) : TSynCustomHighlighter(AOwner) +{ + FCaseSensitive = false; + FKeywords = new TStringList(); + FKeywords->Sorted = true; + FKeywords->Duplicates = System::Classes::dupIgnore; + FEnglishKeywords = new TStringList(); + FEnglishKeywords->Sorted = true; + FEnglishKeywords->Duplicates = System::Classes::dupIgnore; + FAnnotations = new TStringList(); + FAnnotations->Sorted = true; + FAnnotations->Duplicates = System::Classes::dupIgnore; + + FCommentAttri = new TSynHighlighterAttributes(L"Comment", L"Comment"); + FDirectiveAttri = new TSynHighlighterAttributes(L"Directive", L"Directive"); + FIdentifierAttri = new TSynHighlighterAttributes(L"Identifier", L"Identifier"); + FKeyAttri = new TSynHighlighterAttributes(L"Key", L"Key"); + FNumberAttri = new TSynHighlighterAttributes(L"Number", L"Number"); + FSpaceAttri = new TSynHighlighterAttributes(L"Space", L"Space"); + FStringAttri = new TSynHighlighterAttributes(L"String", L"String"); + FSymbolAttri = new TSynHighlighterAttributes(L"Symbol", L"Symbol"); + FAnnotationAttri = new TSynHighlighterAttributes(L"Annotation", L"Annotation"); + + FCommentAttri->Foreground = clGreen; + FCommentAttri->Style = TFontStyles() << fsItalic; + FDirectiveAttri->Foreground = clTeal; + FIdentifierAttri->Foreground = clWindowText; + FKeyAttri->Foreground = clNavy; + FKeyAttri->Style = TFontStyles() << fsBold; + FNumberAttri->Foreground = clPurple; + FSpaceAttri->Foreground = clWindowText; + FStringAttri->Foreground = clMaroon; + FSymbolAttri->Foreground = clGrayText; + FAnnotationAttri->Foreground = clOlive; + + AddAttribute(FCommentAttri); + AddAttribute(FDirectiveAttri); + AddAttribute(FIdentifierAttri); + AddAttribute(FKeyAttri); + AddAttribute(FNumberAttri); + AddAttribute(FSpaceAttri); + AddAttribute(FStringAttri); + AddAttribute(FSymbolAttri); + AddAttribute(FAnnotationAttri); + + LoadRussianKeywords(); + LoadEnglishKeywordPlaceholders(); + LoadAnnotations(); + SetAttributesOnChange(DefHighlightChange); +} + +__fastcall TSyn1CSyn::~TSyn1CSyn() +{ + delete FKeywords; + delete FEnglishKeywords; + delete FAnnotations; +} + +String __fastcall TSyn1CSyn::GetLanguageName() +{ + return L"1C"; +} + +String __fastcall TSyn1CSyn::GetFriendlyLanguageName() +{ + return L"1C:Enterprise"; +} + +void __fastcall TSyn1CSyn::LoadRussianKeywords() +{ + const wchar_t* keywords[] = { + L"\u0430", L"\u0432", L"\u0432\u043E\u0437\u0432\u0440\u0430\u0442", + L"\u0432\u044B\u0437\u0432\u0430\u0442\u044C\u0438\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435", + L"\u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C", + L"\u0434\u043B\u044F", L"\u0434\u043E", L"\u0435\u0441\u043B\u0438", L"\u0438", L"\u0438\u043B\u0438", + L"\u0438\u043D\u0430\u0447\u0435", L"\u0438\u043D\u0430\u0447\u0435\u0435\u0441\u043B\u0438", + L"\u0438\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435", L"\u0438\u0441\u0442\u0438\u043D\u0430", + L"\u043A\u0430\u0436\u0434\u043E\u0433\u043E", L"\u043A\u043E\u043D\u0435\u0446\u0435\u0441\u043B\u0438", + L"\u043A\u043E\u043D\u0435\u0446\u043F\u043E\u043F\u044B\u0442\u043A\u0438", + L"\u043A\u043E\u043D\u0435\u0446\u043F\u0440\u043E\u0446\u0435\u0434\u0443\u0440\u044B", + L"\u043A\u043E\u043D\u0435\u0446\u0444\u0443\u043D\u043A\u0446\u0438\u0438", + L"\u043A\u043E\u043D\u0435\u0446\u0446\u0438\u043A\u043B\u0430", L"\u043B\u043E\u0436\u044C", + L"\u043D\u0435", L"\u043D\u0435\u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0435\u043D\u043E", + L"\u043D\u043E\u0432\u044B\u0439", L"\u043F\u0435\u0440\u0435\u0439\u0442\u0438", L"\u043F\u0435\u0440\u0435\u043C", + L"\u043F\u043E", L"\u043F\u043E\u043A\u0430", L"\u043F\u043E\u043F\u044B\u0442\u043A\u0430", + L"\u043F\u0440\u0435\u0440\u0432\u0430\u0442\u044C", L"\u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C", + L"\u043F\u0440\u043E\u0446\u0435\u0434\u0443\u0440\u0430", L"\u0442\u043E\u0433\u0434\u0430", + L"\u0444\u0443\u043D\u043A\u0446\u0438\u044F", L"\u0446\u0438\u043A\u043B", L"\u044D\u043A\u0441\u043F\u043E\u0440\u0442" + }; + + for (int i = 0; i < sizeof(keywords) / sizeof(keywords[0]); ++i) + FKeywords->Add(keywords[i]); +} + +void __fastcall TSyn1CSyn::LoadEnglishKeywordPlaceholders() +{ + // English 1C keywords can be enabled later by filling this list. +} + +void __fastcall TSyn1CSyn::LoadAnnotations() +{ + const wchar_t* annotations[] = { + L"&\u043D\u0430\u043A\u043B\u0438\u0435\u043D\u0442\u0435", + L"&\u043D\u0430\u0441\u0435\u0440\u0432\u0435\u0440\u0435", + L"&\u043D\u0430\u0441\u0435\u0440\u0432\u0435\u0440\u0435\u0431\u0435\u0437\u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430", + L"&\u043D\u0430\u043A\u043B\u0438\u0435\u043D\u0442\u0435\u043D\u0430\u0441\u0435\u0440\u0432\u0435\u0440\u0435", + L"&\u043F\u0435\u0440\u0435\u0434", L"&\u043F\u043E\u0441\u043B\u0435" + }; + + for (int i = 0; i < sizeof(annotations) / sizeof(annotations[0]); ++i) + FAnnotations->Add(annotations[i]); +} + +bool __fastcall TSyn1CSyn::IsIdentifierChar(WideChar ch) const +{ + return (ch == L'_') + || (ch >= L'0' && ch <= L'9') + || (ch >= L'a' && ch <= L'z') + || (ch >= L'A' && ch <= L'Z') + || (ch >= L'\u0430' && ch <= L'\u044F') + || (ch >= L'\u0410' && ch <= L'\u042F') + || (ch == L'\u0451') + || (ch == L'\u0401'); +} + +bool __fastcall TSyn1CSyn::IsKeywordToken(const String& token) const +{ + return FKeywords->IndexOf(token) >= 0 || FEnglishKeywords->IndexOf(token) >= 0; +} + +bool __fastcall TSyn1CSyn::IsAnnotationToken(const String& token) const +{ + return FAnnotations->IndexOf(token) >= 0; +} + +bool __fastcall TSyn1CSyn::IsIdentChar(WideChar AChar) +{ + return IsIdentifierChar(AChar); +} + +bool __fastcall TSyn1CSyn::IsKeyword(const String AKeyword) +{ + return IsKeywordToken(AKeyword.LowerCase()); +} + +String __fastcall TSyn1CSyn::GetKeyWords(int TokenKind) +{ + if (TokenKind == tk1CKeyword) + return FKeywords->CommaText; + if (TokenKind == tk1CAnnotation) + return FAnnotations->CommaText; + + return L""; +} + +String __fastcall TSyn1CSyn::GetSampleSource() +{ + return L"&\u041D\u0430\u0421\u0435\u0440\u0432\u0435\u0440\u0435\n" + L"\u041F\u0440\u043E\u0446\u0435\u0434\u0443\u0440\u0430 \u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C\u0414\u0430\u043D\u043D\u044B\u0435() \u042D\u043A\u0441\u043F\u043E\u0440\u0442\n" + L" // \u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439\n" + L" \u0415\u0441\u043B\u0438 \u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0435 = 10 \u0422\u043E\u0433\u0434\u0430\n" + L" \u0421\u043E\u043E\u0431\u0449\u0438\u0442\u044C(\"\u0413\u043E\u0442\u043E\u0432\u043E \"\"\u041E\u041A\"\");\n" + L" \u041A\u043E\u043D\u0435\u0446\u0415\u0441\u043B\u0438;\n" + L"\u041A\u043E\u043D\u0435\u0446\u041F\u0440\u043E\u0446\u0435\u0434\u0443\u0440\u044B"; +} + +TSynHighlighterAttributes* __fastcall TSyn1CSyn::GetDefaultAttribute(int Index) +{ + switch (Index) + { + case SYN_ATTR_COMMENT: return FCommentAttri; + case SYN_ATTR_IDENTIFIER: return FIdentifierAttri; + case SYN_ATTR_KEYWORD: return FKeyAttri; + case SYN_ATTR_STRING: return FStringAttri; + case SYN_ATTR_WHITESPACE: return FSpaceAttri; + case SYN_ATTR_SYMBOL: return FSymbolAttri; + default: return FIdentifierAttri; + } +} + +bool __fastcall TSyn1CSyn::GetEol() +{ + return Run == FLineLen + 1; +} + +String __fastcall TSyn1CSyn::GetToken() +{ + int len = Run - FTokenPos; + if (len <= 0 || FTokenPos >= FLineLen) + return L""; + + if (FTokenPos + len > FLineLen) + len = FLineLen - FTokenPos; + + return FLineStr.SubString(FTokenPos + 1, len); +} + +TSynHighlighterAttributes* __fastcall TSyn1CSyn::GetTokenAttribute() +{ + switch (FTokenID) + { + case tk1CComment: return FCommentAttri; + case tk1CDirective: return FDirectiveAttri; + case tk1CIdentifier: return FIdentifierAttri; + case tk1CKeyword: return FKeyAttri; + case tk1CNumber: return FNumberAttri; + case tk1CSpace: return FSpaceAttri; + case tk1CString: return FStringAttri; + case tk1CSymbol: return FSymbolAttri; + case tk1CAnnotation: return FAnnotationAttri; + default: return FIdentifierAttri; + } +} + +int __fastcall TSyn1CSyn::GetTokenKind() +{ + return FTokenID; +} + +int __fastcall TSyn1CSyn::GetTokenPos() +{ + return FTokenPos; +} + +void __fastcall TSyn1CSyn::SetLine(const String Value, int LineNumber) +{ + FLineStr = Value; + FCasedLineStr = Value.LowerCase(); + FLine = FLineStr.c_str(); + FCasedLine = FCasedLineStr.c_str(); + FLineLen = FLineStr.Length(); + FLineNumber = LineNumber; + Run = 0; + FOldRun = Run; + FTokenPos = 0; + FTokenID = tk1CNull; +} + +void __fastcall TSyn1CSyn::NullProc() +{ + FTokenID = tk1CNull; + ++Run; +} + +void __fastcall TSyn1CSyn::SpaceProc() +{ + FTokenID = tk1CSpace; + while (Run < FLineLen && FLine[Run] <= L' ') + ++Run; +} + +void __fastcall TSyn1CSyn::CommentProc() +{ + FTokenID = tk1CComment; + Run = FLineLen; +} + +void __fastcall TSyn1CSyn::StringProc() +{ + FTokenID = tk1CString; + ++Run; + while (Run < FLineLen) + { + if (FLine[Run] == L'"') + { + ++Run; + if (Run < FLineLen && FLine[Run] == L'"') + { + ++Run; + continue; + } + break; + } + ++Run; + } +} + +void __fastcall TSyn1CSyn::NumberProc() +{ + FTokenID = tk1CNumber; + while (Run < FLineLen && FLine[Run] >= L'0' && FLine[Run] <= L'9') + ++Run; + if (Run < FLineLen && (FLine[Run] == L'.' || FLine[Run] == L',')) + { + ++Run; + while (Run < FLineLen && FLine[Run] >= L'0' && FLine[Run] <= L'9') + ++Run; + } +} + +void __fastcall TSyn1CSyn::DirectiveProc() +{ + FTokenID = tk1CDirective; + ++Run; + while (Run < FLineLen && IsIdentifierChar(FCasedLine[Run])) + ++Run; +} + +void __fastcall TSyn1CSyn::AnnotationProc() +{ + ++Run; + while (Run < FLineLen && IsIdentifierChar(FCasedLine[Run])) + ++Run; + + String token = GetToken().LowerCase(); + FTokenID = IsAnnotationToken(token) ? tk1CAnnotation : tk1CSymbol; +} + +void __fastcall TSyn1CSyn::IdentifierProc() +{ + while (Run < FLineLen && IsIdentifierChar(FCasedLine[Run])) + ++Run; + + String token = GetToken().LowerCase(); + FTokenID = IsKeywordToken(token) ? tk1CKeyword : tk1CIdentifier; +} + +void __fastcall TSyn1CSyn::SymbolProc() +{ + FTokenID = tk1CSymbol; + ++Run; +} + +void __fastcall TSyn1CSyn::Next() +{ + FTokenPos = Run; + if (Run >= FLineLen) + { + NullProc(); + TSynCustomHighlighter::Next(); + return; + } + + WideChar ch = FLine[Run]; + if (ch <= L' ') + SpaceProc(); + else if (ch == L'/' && Run + 1 < FLineLen && FLine[Run + 1] == L'/') + CommentProc(); + else if (ch == L'"') + StringProc(); + else if (ch >= L'0' && ch <= L'9') + NumberProc(); + else if (ch == L'#') + DirectiveProc(); + else if (ch == L'&') + AnnotationProc(); + else if (IsIdentifierChar(FCasedLine[Run]) && !(FCasedLine[Run] >= L'0' && FCasedLine[Run] <= L'9')) + IdentifierProc(); + else + SymbolProc(); + + TSynCustomHighlighter::Next(); +} diff --git a/src/SynHighlighter1C.h b/src/SynHighlighter1C.h new file mode 100644 index 0000000..2c30bd9 --- /dev/null +++ b/src/SynHighlighter1C.h @@ -0,0 +1,92 @@ +//--------------------------------------------------------------------------- + +#ifndef SynHighlighter1CH +#define SynHighlighter1CH +//--------------------------------------------------------------------------- + +#include +#include +#include +#include "SynEditHighlighter.hpp" + +enum Ttk1CTokenKind +{ + tk1CComment, + tk1CDirective, + tk1CIdentifier, + tk1CKeyword, + tk1CNull, + tk1CNumber, + tk1CSpace, + tk1CString, + tk1CSymbol, + tk1CAnnotation, + tk1CUnknown +}; + +class TSyn1CSyn : public TSynCustomHighlighter +{ +private: + Ttk1CTokenKind FTokenID; + TSynHighlighterAttributes* FCommentAttri; + TSynHighlighterAttributes* FDirectiveAttri; + TSynHighlighterAttributes* FIdentifierAttri; + TSynHighlighterAttributes* FKeyAttri; + TSynHighlighterAttributes* FNumberAttri; + TSynHighlighterAttributes* FSpaceAttri; + TSynHighlighterAttributes* FStringAttri; + TSynHighlighterAttributes* FSymbolAttri; + TSynHighlighterAttributes* FAnnotationAttri; + TStringList* FKeywords; + TStringList* FEnglishKeywords; + TStringList* FAnnotations; + + bool __fastcall IsIdentifierChar(WideChar ch) const; + bool __fastcall IsKeywordToken(const String& token) const; + bool __fastcall IsAnnotationToken(const String& token) const; + void __fastcall LoadRussianKeywords(); + void __fastcall LoadEnglishKeywordPlaceholders(); + void __fastcall LoadAnnotations(); + void __fastcall NullProc(); + void __fastcall SpaceProc(); + void __fastcall CommentProc(); + void __fastcall StringProc(); + void __fastcall NumberProc(); + void __fastcall DirectiveProc(); + void __fastcall AnnotationProc(); + void __fastcall IdentifierProc(); + void __fastcall SymbolProc(); + +protected: + virtual TSynHighlighterAttributes* __fastcall GetDefaultAttribute(int Index); + virtual String __fastcall GetSampleSource(); + +public: + __fastcall virtual TSyn1CSyn(TComponent* AOwner); + __fastcall virtual ~TSyn1CSyn(); + __classmethod virtual String __fastcall GetLanguageName(); + __classmethod virtual String __fastcall GetFriendlyLanguageName(); + virtual bool __fastcall GetEol(); + virtual String __fastcall GetToken(); + virtual TSynHighlighterAttributes* __fastcall GetTokenAttribute(); + virtual int __fastcall GetTokenKind(); + virtual int __fastcall GetTokenPos(); + virtual bool __fastcall IsIdentChar(WideChar AChar); + virtual bool __fastcall IsKeyword(const String AKeyword); + virtual void __fastcall Next(); + virtual void __fastcall SetLine(const String Value, int LineNumber); + virtual String __fastcall GetKeyWords(int TokenKind); + + __property TSynHighlighterAttributes* CommentAttri = {read=FCommentAttri}; + __property TSynHighlighterAttributes* DirectiveAttri = {read=FDirectiveAttri}; + __property TSynHighlighterAttributes* IdentifierAttri = {read=FIdentifierAttri}; + __property TSynHighlighterAttributes* KeyAttri = {read=FKeyAttri}; + __property TSynHighlighterAttributes* NumberAttri = {read=FNumberAttri}; + __property TSynHighlighterAttributes* SpaceAttri = {read=FSpaceAttri}; + __property TSynHighlighterAttributes* StringAttri = {read=FStringAttri}; + __property TSynHighlighterAttributes* SymbolAttri = {read=FSymbolAttri}; + __property TSynHighlighterAttributes* AnnotationAttri = {read=FAnnotationAttri}; +}; + +//--------------------------------------------------------------------------- +#endif From d77bf5fe6930499e7bf04c4ef22db6154aaeb426 Mon Sep 17 00:00:00 2001 From: fishca Date: Thu, 30 Apr 2026 17:29:03 +0300 Subject: [PATCH 2/4] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=80=D0=B0=D1=81=D0=BF=D0=B0=D0=BA=D0=BE=D0=B2?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=B2=20=D0=BE=D1=82=D0=B4=D0=B5=D0=BB=D1=8C?= =?UTF-8?q?=D0=BD=D0=BE=D0=BC=20=D0=BF=D0=BE=D1=82=D0=BE=D0=BA=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/MainUnit.cpp | 51 ++++++++++++++++++++++++++++++++++++++++++------ src/MainUnit.h | 2 ++ 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/MainUnit.cpp b/src/MainUnit.cpp index f0d254d..32b191f 100644 --- a/src/MainUnit.cpp +++ b/src/MainUnit.cpp @@ -8,6 +8,7 @@ #include +#include #include #include #include @@ -561,6 +562,30 @@ static void AddConditionalInfoMessageParams(Messager* mess, const String& descri mess->AddMessage_(description, msInfo, parname1, par1, parname2, par2, parname3, par3); } +static void StartConfigUnpackThread(const String& cfFileName, const String& sourceCfDir) +{ + const String targetDir = sourceCfDir; + const std::string cfFileNameStd = AnsiString(cfFileName).c_str(); + const std::string targetDirStd = AnsiString(sourceCfDir).c_str(); + + TThread* unpackThread = TThread::CreateAnonymousThread([targetDir, cfFileNameStd, targetDirStd]() + { + try + { + if (TDirectory::Exists(targetDir)) + TDirectory::Delete(targetDir, true); + + std::vector filter; + v8unpack::Parse(cfFileNameStd, targetDirStd, filter); + } + catch (...) + { + } + }); + unpackThread->FreeOnTerminate = true; + unpackThread->Start(); +} + static String FormatHeapStatus() { THeapStatus hs = GetHeapStatus(); @@ -599,6 +624,7 @@ void __fastcall TMainForm::SetDefaultHighlightSettingsControls() if (HighlightAnnotationColorBox) HighlightAnnotationColorBox->Selected = DefaultHighlightAnnotationColor; if (HighlightKeywordBoldCheckBox) HighlightKeywordBoldCheckBox->Checked = true; if (HighlightCommentItalicCheckBox) HighlightCommentItalicCheckBox->Checked = true; + if (UnpackCheckBox) UnpackCheckBox->Checked = false; } void __fastcall TMainForm::ApplyHighlightSettings() @@ -656,6 +682,7 @@ void __fastcall TMainForm::LoadHighlightSettings() HighlightAnnotationColorBox->Selected = (TColor)ini->ReadInteger(L"Colors", L"Annotation", DefaultHighlightAnnotationColor); HighlightKeywordBoldCheckBox->Checked = ini->ReadBool(L"Style", L"KeywordBold", true); HighlightCommentItalicCheckBox->Checked = ini->ReadBool(L"Style", L"CommentItalic", true); + UnpackCheckBox->Checked = ini->ReadBool(L"Style", L"Unpack", false); HighlightSettingsLoading = false; ApplyHighlightSettings(); @@ -673,6 +700,7 @@ void __fastcall TMainForm::SaveHighlightSettings() ini->WriteInteger(L"Colors", L"Annotation", HighlightAnnotationColorBox->Selected); ini->WriteBool(L"Style", L"KeywordBold", HighlightKeywordBoldCheckBox->Checked); ini->WriteBool(L"Style", L"CommentItalic", HighlightCommentItalicCheckBox->Checked); + ini->WriteBool(L"Style", L"Unpack", UnpackCheckBox->Checked); } void __fastcall TMainForm::HighlightSettingsChanged(TObject *Sender) @@ -698,7 +726,7 @@ void __fastcall TMainForm::CreateHighlightSettingsTab() TTabSheet* previousActivePage = pagesEdit ? pagesEdit->ActivePage : nullptr; HighlightSettingsTab = new TTabSheet(this); HighlightSettingsTab->PageControl = pagesEdit; - HighlightSettingsTab->Caption = L"Подсветка"; + HighlightSettingsTab->Caption = L"Настройка"; TPanel* panel = new TPanel(HighlightSettingsTab); panel->Parent = HighlightSettingsTab; @@ -752,6 +780,15 @@ void __fastcall TMainForm::CreateHighlightSettingsTab() HighlightCommentItalicCheckBox->Checked = true; HighlightCommentItalicCheckBox->OnClick = HighlightSettingsChanged; + UnpackCheckBox = new TCheckBox(panel); + UnpackCheckBox->Parent = panel; + UnpackCheckBox->Left = 18; + UnpackCheckBox->Top = 316; + UnpackCheckBox->Width = 250; + UnpackCheckBox->Caption = L"Распаковывать в исходники"; + UnpackCheckBox->Checked = false; + UnpackCheckBox->OnClick = HighlightSettingsChanged; + TButton* resetButton = new TButton(panel); resetButton->Parent = panel; resetButton->Left = 18; @@ -774,13 +811,15 @@ void __fastcall TMainForm::CreateHighlightSettingsTab() HighlightPreviewMemo->OnScanForFoldRanges = ModuleMemoScanForFoldRanges; HighlightPreviewMemo->UseCodeFolding = true; HighlightPreviewMemo->Lines->Text = + L"#Область ПримерКода\n" L"&НаСервере\n" L"Процедура ОбновитьДанные() Экспорт\n" L" // Комментарий\n" L" Если Значение = 10 Тогда\n" L" Сообщить(\"Готово\");\n" L" КонецЕсли;\n" - L"КонецПроцедуры"; + L"КонецПроцедуры\n" + L"#КонецОбласти"; HighlightPreviewMemo->Invalidate(); LoadHighlightSettings(); @@ -1439,11 +1478,11 @@ void __fastcall TMainForm::ActionFileOpenExecute(TObject *Sender) AddConditionalInfoMessage(mess, L"ActionFileOpenExecute: построение дерева интерфейса"); AdvanceLoadProgress(L"Построение дерева метаданных..."); - std::vector filter; + const String sourceCfDir = TPath::Combine(ExtractFilePath(ParamStr(0)), L"SourceCF"); - if (TDirectory::Exists(sourceCfDir)) - TDirectory::Delete(sourceCfDir, true); - v8unpack::Parse(AnsiString(EditNameCF->Text).c_str(), AnsiString(sourceCfDir).c_str(), filter); + + if (UnpackCheckBox->Checked) + StartConfigUnpackThread(EditNameCF->Text, sourceCfDir); VirtualStringTreeValue1C->BeginUpdate(); try diff --git a/src/MainUnit.h b/src/MainUnit.h index 20ff6dc..19aaefb 100644 --- a/src/MainUnit.h +++ b/src/MainUnit.h @@ -139,6 +139,8 @@ class TMainForm : public TForm TColorBox *HighlightAnnotationColorBox; TCheckBox *HighlightKeywordBoldCheckBox; TCheckBox *HighlightCommentItalicCheckBox; + TCheckBox *UnpackCheckBox; + TSynMemo *HighlightPreviewMemo; bool HighlightSettingsLoading; TTimer *ModuleSelectionTimer; From 8966cb46c0486e6ae77c6b758af2391b46243999 Mon Sep 17 00:00:00 2001 From: fishca Date: Thu, 30 Apr 2026 17:32:28 +0300 Subject: [PATCH 3/4] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D1=81=D1=83=D1=89=D0=B5=D1=81=D1=82=D0=B2=D1=83=D1=8E?= =?UTF-8?q?=D1=89=D0=B5=D0=B9=20=D0=B4=D0=B8=D1=80=D0=B5=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=B8=20SourceCF=20=D0=B1=D0=B5=D0=B7=D1=83=D1=81?= =?UTF-8?q?=D0=BB=D0=BE=D0=B2=D0=BD=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/MainUnit.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/MainUnit.cpp b/src/MainUnit.cpp index 32b191f..e9ade36 100644 --- a/src/MainUnit.cpp +++ b/src/MainUnit.cpp @@ -1481,6 +1481,9 @@ void __fastcall TMainForm::ActionFileOpenExecute(TObject *Sender) const String sourceCfDir = TPath::Combine(ExtractFilePath(ParamStr(0)), L"SourceCF"); + if (TDirectory::Exists(sourceCfDir)) + TDirectory::Delete(sourceCfDir, true); + if (UnpackCheckBox->Checked) StartConfigUnpackThread(EditNameCF->Text, sourceCfDir); From 669bda667ecf3e2dc976e8252e9b49ffaf04a30f Mon Sep 17 00:00:00 2001 From: fishca Date: Wed, 6 May 2026 16:44:20 +0300 Subject: [PATCH 4/4] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BF=D0=B5=D1=80=D0=B2=D0=BE=D0=BD=D0=B0=D1=87?= =?UTF-8?q?=D0=B0=D0=BB=D1=8C=D0=BD=D0=B0=D1=8F=20=D0=BF=D0=BE=D0=B4=D0=B4?= =?UTF-8?q?=D0=B5=D1=80=D0=B6=D0=BA=D0=B0=20=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE?= =?UTF-8?q?=D0=B6=D0=BD=D0=BE=D1=81=D1=82=D0=B8=20=D1=80=D0=B5=D0=B4=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Refactoring_MD.md | 570 ------------------ src/BaseMetadataObject.cpp | 49 ++ src/BaseMetadataObject.h | 12 + src/ChartOfAccounts.cpp | 35 +- src/ChartOfCharacteristicTypes.cpp | 35 +- src/CommonForms.cpp | 125 ++-- src/CommonForms.h | 11 +- src/CommonModules.cpp | 116 ++-- src/CommonModules.h | 11 +- src/MainUnit.cpp | 293 +++++++-- src/MainUnit.dfm | 24 + src/MainUnit.h | 37 +- src/MetadataObjectInformationRegister.cpp | 30 +- src/MetadataObjectWithSections.cpp | 142 ++++- src/MetadataObjectWithSections.h | 15 + src/MetadataTreeBuilder.cpp | 243 ++++---- src/MetadataTreeBuilder.h | 54 +- src/ModuleTextStorage.cpp | 699 ++++++++++++++++++++++ src/ModuleTextStorage.h | 69 +++ 19 files changed, 1664 insertions(+), 906 deletions(-) delete mode 100644 Refactoring_MD.md create mode 100644 src/ModuleTextStorage.cpp create mode 100644 src/ModuleTextStorage.h diff --git a/Refactoring_MD.md b/Refactoring_MD.md deleted file mode 100644 index 59c7f22..0000000 --- a/Refactoring_MD.md +++ /dev/null @@ -1,570 +0,0 @@ -# План рефакторинга построения дерева метаданных - -## Контекст - -В `src/MainUnit.cpp` есть группа процедур, которые вручную строят дерево метаданных в `VirtualStringTreeValue1C`: - -- `fillInformationRegisterTree` -- `fillAccumulationRegisterTree` -- `fillAccountingRegisterTree` -- `fillCalculationRegisterTree` -- `fillCatalogsTree` -- `fillChartAccTree` -- `fillJournalTree` -- частично `fillEnumTree` - -Наиболее показательная функция — `fillInformationRegisterTree`: она почти полностью повторяет шаблон, уже встречающийся в других `fill*Tree`-процедурах. - -## Основные проблемы текущей реализации - -### 1. Сильное дублирование кода - -Почти каждая процедура содержит один и тот же сценарий: - -1. Инициализация корневого узла: - - `Name` - - `Age` - - `ImgIndex` -2. Создание секции: - - `Измерения` - - `Ресурсы` - - `Реквизиты` - - `Формы` - - `Команды` - - `Макеты` -3. Цикл по коллекции и создание дочерних узлов. - -Отличается в основном только набор секций, текст заголовка и индекс иконки. - -### 2. Слишком низкий уровень абстракции - -В каждой функции вручную повторяются вызовы: - -- `AddChild(...)` -- `GetNodeData(...)` -- присваивание `Name/Age/ImgIndex` - -Из-за этого бизнес-структура дерева теряется в техническом шуме. - -### 3. Длинные сигнатуры функций - -Например, `fillAccountingRegisterTree` принимает длинный список параметров: - -- `name` -- `attributes` -- `dimensions` -- `resources` -- `accountingFlags` -- `dimensionAccountingFlags` -- `forms` -- `comands` -- `moxels` - -Такие сигнатуры трудно читать, сопровождать и безопасно вызывать. - -### 4. Магические числа - -В коде напрямую используются значения иконок и дефолтного возраста: - -- `83` -- `82` -- `86` -- `98` -- `79` -- `10` -- `11` -- `118` -- `119` -- `30` - -Это ухудшает читаемость и делает код менее самодокументируемым. - -### 5. Смешение ответственности - -`MainUnit.cpp` одновременно отвечает за: - -- знание структуры метаданных; -- знание текстов секций; -- знание иконок; -- непосредственный рендеринг узлов VCL-дерева. - -Из-за этого `TMainForm` перегружен логикой, которую лучше вынести в отдельный builder/helper-модуль. - -### 6. Неунифицированная работа с коллекциями - -В части случаев элементы представлены объектами с полем `name`, в части — строками (`fillEnumTree`). -Это мешает переиспользовать общий код без дополнительного уровня абстракции. - ---- - -## Цель рефакторинга - -Сделать построение дерева: - -- менее дублирующимся; -- декларативным; -- расширяемым; -- удобным для поддержки; -- меньше связанным с `TMainForm`. - ---- - -## Предлагаемая стратегия рефакторинга - -## Этап 1. Вынести базовые helper-функции - -### Задача - -Спрятать низкоуровневые операции работы с деревом за небольшим API. - -### Целевые функции - -```cpp -void initNode(VirtualTreeData* data, const String& name, int imageIndex, int age = DefaultTreeNodeAge); - -PVirtualNode addChildNode(TVirtualStringTree* tree, - PVirtualNode parent, - const String& name, - int imageIndex, - int age = DefaultTreeNodeAge); -``` - -### Что это даст - -- уберёт повторяющиеся присваивания `Name/Age/ImgIndex`; -- упростит чтение кода в `fill*Tree`; -- создаст основу для следующего этапа. - ---- - -## Этап 2. Вынести общую логику добавления секции - -### Задача - -Унифицировать код вида: - -- создать секцию; -- пройти по коллекции; -- для каждого элемента добавить дочерний узел. - -### Целевой API - -Практический и наиболее удобный вариант — шаблонная функция: - -```cpp -template -void addSection(TVirtualStringTree* tree, - PVirtualNode parent, - const String& sectionName, - int sectionImageIndex, - int itemImageIndex, - const Collection& items, - NameGetter getName, - int age = DefaultTreeNodeAge) -{ - PVirtualNode sectionNode = addChildNode(tree, parent, sectionName, sectionImageIndex, age); - - for (const auto& item : items) - addChildNode(tree, sectionNode, getName(item), itemImageIndex, age); -} -``` - -### Что это даст - -- устранит основной объём копипасты; -- позволит одинаково обрабатывать коллекции объектов и коллекции строк; -- сделает функции `fill*Tree` декларативными. - ---- - -## Этап 3. Ввести именованные константы для иконок и стандартных значений - -### Задача - -Заменить магические числа понятными именами. - -### Целевой эскиз - -```cpp -namespace TreeImage { - constexpr int Root = 72; - constexpr int Attributes = 83; - constexpr int TabularSections = 82; - constexpr int Forms = 86; - constexpr int Commands = 98; - constexpr int Layouts = 79; - constexpr int Dimensions = 10; - constexpr int Resources = 11; - constexpr int AccountingFlags = 118; - constexpr int SubcontoFlags = 119; - constexpr int JournalColumns = 6; -} - -constexpr int DefaultTreeNodeAge = 30; -``` - -### Что это даст - -- код станет понятнее без поиска по проекту; -- исчезнет зависимость от “неочевидных” чисел; -- будет проще менять отображение централизованно. - ---- - -## Этап 4. Объединить однотипные процедуры в общую реализацию - -### Кандидаты на объединение - -Практически идентичны: - -- `fillInformationRegisterTree` -- `fillAccumulationRegisterTree` -- `fillCalculationRegisterTree` - -Близки по структуре: - -- `fillAccountingRegisterTree` -- `fillChartAccTree` - -### Предлагаемый общий API - -#### Вариант через декларативное описание секций - -```cpp -struct TreeSectionDescriptor { - String sectionName; - int sectionImageIndex; - int itemImageIndex; - std::function renderItems; -}; -``` - -```cpp -void fillRegisterTreeCommon(TVirtualStringTree* tree, - PVirtualNode objectNode, - VirtualTreeData* objectData, - const String& objectName, - int objectImageIndex, - const std::vector& sections); -``` - -### Базовая реализация - -```cpp -void fillRegisterTreeCommon(TVirtualStringTree* tree, - PVirtualNode objectNode, - VirtualTreeData* objectData, - const String& objectName, - int objectImageIndex, - const std::vector& sections) -{ - initNode(objectData, objectName, objectImageIndex); - - for (const auto& section : sections) - { - PVirtualNode sectionNode = addChildNode(tree, - objectNode, - section.sectionName, - section.sectionImageIndex); - section.renderItems(sectionNode); - } -} -``` - -### Комментарий - -Этот вариант удобен там, где секции действительно различаются только набором данных и способом обхода. - -Однако на практике в текущем проекте **шаблонный `addSection(...)` будет проще и легче внедряется поэтапно**, чем тотальный переход на `TreeSectionDescriptor` с `std::function`. - -Поэтому рекомендуемый путь такой: - -1. сначала внедрить `initNode` / `addChildNode` / `addSection`; -2. затем уже решать, нужен ли в проекте полноценный `TreeSectionDescriptor`. - ---- - -## Этап 5. Сократить сигнатуры `fill*Tree` - -### Проблема - -Сейчас функции принимают слишком много параметров. - -### Варианты улучшения - -#### Вариант A. Передавать сам объект метаданных - -Например: - -```cpp -void fillInformationRegisterTree(PVirtualNode childNode, - VirtualTreeData* childData, - int imgIndex, - const TInformationRegisters& reg); -``` - -#### Вариант B. Ввести адаптер/descriptor данных - -```cpp -struct RegisterTreeData { - String name; - const std::vector>& attributes; - const std::vector>& dimensions; - const std::vector>& resources; - const std::vector>& forms; - const std::vector>& commands; - const std::vector>& moxels; -}; -``` - -### Что это даст - -- меньше ошибок при вызове; -- понятнее зависимость функции от входных данных; -- проще переносить код в отдельный модуль. - ---- - -## Этап 6. Вынести builder в отдельный модуль - -### Цель - -Разгрузить `MainUnit.cpp` и оставить в `TMainForm` только координацию UI. - -### Возможные файлы - -- `MetadataTreeBuilder.h` -- `MetadataTreeBuilder.cpp` - -### Что вынести - -- константы иконок; -- helper-функции построения узлов; -- `addSection(...)`; -- `fillRegisterTreeCommon(...)`; -- при необходимости — адаптеры для разных типов метаданных. - ---- - -## Целевой эскиз API - -Ниже приведён рекомендуемый набор интерфейсов, который можно взять как основу. - -### Константы - -```cpp -namespace TreeImage { - constexpr int Root = 72; - constexpr int Attributes = 83; - constexpr int TabularSections = 82; - constexpr int Forms = 86; - constexpr int Commands = 98; - constexpr int Layouts = 79; - constexpr int Dimensions = 10; - constexpr int Resources = 11; - constexpr int AccountingFlags = 118; - constexpr int SubcontoFlags = 119; - constexpr int JournalColumns = 6; -} - -constexpr int DefaultTreeNodeAge = 30; -``` - -### Базовые операции - -```cpp -void initNode(VirtualTreeData* data, - const String& name, - int imageIndex, - int age = DefaultTreeNodeAge); - -PVirtualNode addChildNode(TVirtualStringTree* tree, - PVirtualNode parent, - const String& name, - int imageIndex, - int age = DefaultTreeNodeAge); -``` - -### Универсальная секция - -```cpp -template -void addSection(TVirtualStringTree* tree, - PVirtualNode parent, - const String& sectionName, - int sectionImageIndex, - int itemImageIndex, - const Collection& items, - NameGetter getName, - int age = DefaultTreeNodeAge); -``` - -### Декларативный descriptor (опциональный следующий шаг) - -```cpp -struct TreeSectionDescriptor { - String sectionName; - int sectionImageIndex; - int itemImageIndex; - std::function renderItems; -}; -``` - -### Общий builder для регистров - -```cpp -void fillRegisterTreeCommon(TVirtualStringTree* tree, - PVirtualNode objectNode, - VirtualTreeData* objectData, - const String& objectName, - int objectImageIndex, - const std::vector& sections); -``` - ---- - -## Пример целевой `fillInformationRegisterTree` - -После первого этапа рефакторинга функция должна стать близкой к такой форме: - -```cpp -void __fastcall TMainForm::fillInformationRegisterTree( - PVirtualNode childNode, - VirtualTreeData* childData, - int imgIndex, - String name, - const std::vector>& attributes, - const std::vector>& dimensions, - const std::vector>& resources, - const std::vector>& forms, - const std::vector>& commands, - const std::vector>& moxels) -{ - initNode(childData, name, imgIndex); - - addSection(VirtualStringTreeValue1C, childNode, - L"Измерения", TreeImage::Dimensions, TreeImage::Dimensions, - dimensions, - [](const auto& item) { return item->name; }); - - addSection(VirtualStringTreeValue1C, childNode, - L"Ресурсы", TreeImage::Resources, TreeImage::Resources, - resources, - [](const auto& item) { return item->name; }); - - addSection(VirtualStringTreeValue1C, childNode, - L"Реквизиты", TreeImage::Attributes, TreeImage::Attributes, - attributes, - [](const auto& item) { return item->name; }); - - addSection(VirtualStringTreeValue1C, childNode, - L"Формы", TreeImage::Forms, TreeImage::Forms, - forms, - [](const auto& item) { return item->name; }); - - addSection(VirtualStringTreeValue1C, childNode, - L"Команды", TreeImage::Commands, TreeImage::Commands, - commands, - [](const auto& item) { return item->name; }); - - addSection(VirtualStringTreeValue1C, childNode, - L"Макеты", TreeImage::Layouts, TreeImage::Layouts, - moxels, - [](const auto& item) { return item->name; }); -} -``` - -### Что это меняет - -- исчезает ручная работа с каждым узлом секции; -- логика функции становится декларацией структуры дерева; -- поддержка и сравнение с другими `fill*Tree` становится проще. - ---- - -## Пример использования `addSection` для строковых коллекций - -Это особенно полезно для `fillEnumTree`, где элементы могут быть строками. - -```cpp -addSection(VirtualStringTreeValue1C, childNode, - L"Значения", TreeImage::Attributes, TreeImage::Attributes, - CurCat->attributes, - [](const auto& item) { return item; }); -``` - -Таким образом один и тот же helper сможет работать и с: - -- `vector>` -- `vector>` -- `vector` - ---- - -## Какие функции стоит переводить в первую очередь - -### Приоритет 1 — почти идентичные - -- `fillInformationRegisterTree` -- `fillAccumulationRegisterTree` -- `fillCalculationRegisterTree` - -### Приоритет 2 — близкие по форме - -- `fillAccountingRegisterTree` -- `fillChartAccTree` -- `fillJournalTree` - -### Приоритет 3 — особые случаи - -- `fillEnumTree` - ---- - -## Рекомендуемый практический порядок работ - -1. Вынести `TreeImage::*` и `DefaultTreeNodeAge`. -2. Реализовать `initNode(...)` и `addChildNode(...)`. -3. Реализовать шаблонный `addSection(...)`. -4. Перевести `fillInformationRegisterTree` как пилотный пример. -5. Перевести `fillAccumulationRegisterTree` и `fillCalculationRegisterTree`. -6. Перевести `fillAccountingRegisterTree` и `fillChartAccTree`. -7. Перевести `fillJournalTree` и `fillEnumTree`. -8. После стабилизации вынести builder в отдельный модуль. - ---- - -## Минимально достаточный результат рефакторинга - -Если нужен компромиссный вариант без большого архитектурного изменения, достаточно сделать следующие шаги: - -- вынести helper-функции; -- убрать магические числа; -- внедрить `addSection(...)`; -- переписать `fillInformationRegisterTree` и 2–3 аналогичные функции. - -Уже это даст: - -- заметное снижение копипасты; -- лучшую читаемость; -- основу для дальнейшего поэтапного улучшения. - ---- - -## Итог - -Оптимальная стратегия для этого участка кода — **переход от ручного императивного построения дерева к небольшому декларативному API**. - -Наиболее практичный первый шаг: - -- `initNode(...)` -- `addChildNode(...)` -- `addSection(...)` - -Наиболее логичный следующий шаг: - -- `fillRegisterTreeCommon(...)` -- `TreeSectionDescriptor` -- вынос builder-логики из `MainUnit.cpp`. - -Такой подход уменьшит дублирование, повысит читаемость и позволит развивать дерево метаданных без дальнейшего разрастания `MainUnit.cpp`. \ No newline at end of file diff --git a/src/BaseMetadataObject.cpp b/src/BaseMetadataObject.cpp index 248915d..514609d 100644 --- a/src/BaseMetadataObject.cpp +++ b/src/BaseMetadataObject.cpp @@ -45,3 +45,52 @@ String __fastcall BaseMetadataObject::GetGUID() return guid; } +bool __fastcall BaseMetadataObject::HasEditableModuleText() +{ + return false; +} + +String __fastcall BaseMetadataObject::GetEditableModuleText() +{ + return L""; +} + +void __fastcall BaseMetadataObject::SetEditableModuleText(const String& value) +{ +} + +bool __fastcall BaseMetadataObject::SaveEditableModuleText(const String& value, String& errorText) +{ + errorText = L"Для этого объекта редактирование модуля не реализовано."; + return false; +} + +ModuleTextLocation __fastcall BaseMetadataObject::GetEditableModuleLocation() +{ + return ModuleTextLocation(); +} + +bool __fastcall BaseMetadataObject::HasEditableModuleText(ModuleTextKind kind) +{ + return HasEditableModuleText(); +} + +String __fastcall BaseMetadataObject::GetEditableModuleText(ModuleTextKind kind) +{ + return GetEditableModuleText(); +} + +void __fastcall BaseMetadataObject::SetEditableModuleText(ModuleTextKind kind, const String& value) +{ + SetEditableModuleText(value); +} + +bool __fastcall BaseMetadataObject::SaveEditableModuleText(ModuleTextKind kind, const String& value, String& errorText) +{ + return SaveEditableModuleText(value, errorText); +} + +ModuleTextLocation __fastcall BaseMetadataObject::GetEditableModuleLocation(ModuleTextKind kind) +{ + return GetEditableModuleLocation(); +} diff --git a/src/BaseMetadataObject.h b/src/BaseMetadataObject.h index 3696a12..d9e76f6 100644 --- a/src/BaseMetadataObject.h +++ b/src/BaseMetadataObject.h @@ -14,6 +14,7 @@ #include "Moxel.h" #include "Tabular.h" #include "Form.h" +#include "ModuleTextStorage.h" //--------------------------------------------------------------------------- // Абстрактный базовый класс для всех объектов метаданных @@ -46,6 +47,17 @@ class BaseMetadataObject : public TObject // Виртуальный метод для инициализации данных из root_data virtual void __fastcall initializeFromTree() = 0; + + virtual bool __fastcall HasEditableModuleText(); + virtual String __fastcall GetEditableModuleText(); + virtual void __fastcall SetEditableModuleText(const String& value); + virtual bool __fastcall SaveEditableModuleText(const String& value, String& errorText); + virtual ModuleTextLocation __fastcall GetEditableModuleLocation(); + virtual bool __fastcall HasEditableModuleText(ModuleTextKind kind); + virtual String __fastcall GetEditableModuleText(ModuleTextKind kind); + virtual void __fastcall SetEditableModuleText(ModuleTextKind kind, const String& value); + virtual bool __fastcall SaveEditableModuleText(ModuleTextKind kind, const String& value, String& errorText); + virtual ModuleTextLocation __fastcall GetEditableModuleLocation(ModuleTextKind kind); }; diff --git a/src/ChartOfAccounts.cpp b/src/ChartOfAccounts.cpp index 2a8e35d..cf1afe3 100644 --- a/src/ChartOfAccounts.cpp +++ b/src/ChartOfAccounts.cpp @@ -4,9 +4,31 @@ #include "Common.h" #include "ChartOfAccounts.h" +#include "ModuleTextStorage.h" //--------------------------------------------------------------------------- #pragma package(smart_init) +namespace +{ + String FindFirstGuid(tree* node) + { + if (!node) + return L""; + + String value = Trim(node->get_value()); + if (ModuleTextStorage::IsGuidLike(value)) + return value; + + for (int i = 0; i < node->get_num_subnode(); i++) + { + String found = FindFirstGuid(node->get_subnode(i)); + if (!found.IsEmpty()) + return found; + } + + return L""; + } +} __fastcall TChartOfAccounts::TChartOfAccounts() : BaseMetadataObject() @@ -126,8 +148,9 @@ void __fastcall TChartOfAccounts::initializeFromTree() curNodeChild = curNodeChild->get_next(); if (curNodeChild) { - String NameForm = GetNameFormPVH(parent, curNodeChild->get_value()); - forms.push_back(std::make_unique(NameForm, "")); + String formGuid = curNodeChild->get_value(); + String NameForm = GetNameFormPVH(parent, formGuid); + forms.push_back(std::make_unique(NameForm, formGuid)); } } @@ -141,10 +164,11 @@ void __fastcall TChartOfAccounts::initializeFromTree() int DeltaCom = CountCom - 2; for (int i = 0; i < CountCom; i++) { - tree* node_com = root_data.get(); - node_com = &(*node_com)[0][3][i+CountCom-DeltaCom][0][1][3][2][9][2]; + tree* commandNode = &(*root_data.get())[0][3][i+CountCom-DeltaCom]; + String commandGuid = FindFirstGuid(commandNode); + tree* node_com = &(*commandNode)[0][1][3][2][9][2]; String NameCom = node_com->get_value(); - comands.push_back(std::make_unique(NameCom, "")); + comands.push_back(std::make_unique(NameCom, commandGuid)); } // Получаем макеты moxels.clear(); @@ -163,4 +187,3 @@ void __fastcall TChartOfAccounts::initializeFromTree() } } } - diff --git a/src/ChartOfCharacteristicTypes.cpp b/src/ChartOfCharacteristicTypes.cpp index 11517b3..7f7a316 100644 --- a/src/ChartOfCharacteristicTypes.cpp +++ b/src/ChartOfCharacteristicTypes.cpp @@ -4,9 +4,32 @@ #include "Common.h" #include "ChartOfCharacteristicTypes.h" +#include "ModuleTextStorage.h" //--------------------------------------------------------------------------- #pragma package(smart_init) +namespace +{ + String FindFirstGuid(tree* node) + { + if (!node) + return L""; + + String value = Trim(node->get_value()); + if (ModuleTextStorage::IsGuidLike(value)) + return value; + + for (int i = 0; i < node->get_num_subnode(); i++) + { + String found = FindFirstGuid(node->get_subnode(i)); + if (!found.IsEmpty()) + return found; + } + + return L""; + } +} + __fastcall TChartOfCharacteristicTypes::TChartOfCharacteristicTypes():BaseMetadataObject() { } @@ -96,8 +119,9 @@ void __fastcall TChartOfCharacteristicTypes::initializeFromTree() curNodeChild = curNodeChild->get_next(); if (curNodeChild) { - String NameForm = GetNameFormPVH(parent, curNodeChild->get_value()); - forms.push_back(std::make_unique(NameForm, "")); + String formGuid = curNodeChild->get_value(); + String NameForm = GetNameFormPVH(parent, formGuid); + forms.push_back(std::make_unique(NameForm, formGuid)); } } @@ -112,10 +136,11 @@ void __fastcall TChartOfCharacteristicTypes::initializeFromTree() int DeltaCom = CountCom - 2; for (int i = 0; i < CountCom; i++) { - tree* node_com = root_data.get(); - node_com = &(*node_com)[0][6][i+CountCom-DeltaCom][0][1][3][2][9][2]; + tree* commandNode = &(*root_data.get())[0][6][i+CountCom-DeltaCom]; + String commandGuid = FindFirstGuid(commandNode); + tree* node_com = &(*commandNode)[0][1][3][2][9][2]; String NameCom = node_com->get_value(); - comands.push_back(std::make_unique(NameCom, "")); + comands.push_back(std::make_unique(NameCom, commandGuid)); } // Получаем макеты auto& moxels = getLayouts(); diff --git a/src/CommonForms.cpp b/src/CommonForms.cpp index 8669c92..8321d47 100644 --- a/src/CommonForms.cpp +++ b/src/CommonForms.cpp @@ -56,24 +56,27 @@ __fastcall TCommonForms::TCommonForms() : BaseMetadataObject() { name = ""; root_data.reset(); - text = L""; - textLoaded = false; + textDocument.text = L""; + textDocument.loaded = false; + textDocument.dirty = false; } __fastcall TCommonForms::TCommonForms(v8catalog* _parent, const String& _guid) : BaseMetadataObject(_parent, _guid) { name = ""; root_data.reset(); - text = L""; - textLoaded = false; + textDocument.text = L""; + textDocument.loaded = false; + textDocument.dirty = false; } __fastcall TCommonForms::TCommonForms(v8catalog* _parent, const String& _guid, const String& _name) : BaseMetadataObject(_parent, _guid, _name) { name = _name; root_data.reset(); - text = L""; - textLoaded = false; + textDocument.text = L""; + textDocument.loaded = false; + textDocument.dirty = false; } __fastcall TCommonForms::~TCommonForms() @@ -92,73 +95,75 @@ void __fastcall TCommonForms::SetFormName(String _name) void __fastcall TCommonForms::LoadTextIfNeeded() { - if (textLoaded) + if (textDocument.loaded) return; - text = L""; - - if ((parent) && (!guid.IsEmpty())) - { - v8file* data_form = parent->GetFile(guid + ".0"); - if (data_form) - { - v8catalog* data_form_cat = new v8catalog(data_form); - if (data_form_cat) - { - auto module_file = data_form_cat->GetFile("module"); - if (module_file) - { - data_form_cat->ClearIs8316(); - TBytes bytes; - TBytesStream* sb = new TBytesStream(bytes); - module_file->SaveToStream(sb); - - TEncoding* enc = nullptr; - TBytes sb_bytes = sb->Bytes; - if (!sb_bytes.empty()) - { - int off = TEncoding::GetBufferEncoding(sb_bytes, enc); - if (off > 0) - { - bytes = TEncoding::Convert(enc, TEncoding::Unicode, sb_bytes, off, sb->Size - off); - if (!bytes.empty()) - text = String((wchar_t*)&bytes[0], bytes.Length / 2); - } - else - { - text = String((char*)sb->Memory, sb->Size); - } - } - - delete sb; - } - - delete data_form_cat; - } - } + textDocument = ModuleTextStorage::LoadCommonForm(parent, guid, name); + textDocument.loaded = true; +} - if (text.IsEmpty()) - { - std::unique_ptr form_tree(get_treeFromV8file(data_form)); - text = GetManagedFormModuleText(form_tree.get()); - if (text.IsEmpty()) - text = FindEmbeddedModuleText(form_tree.get()); - } - } +void __fastcall TCommonForms::RefreshEditableTextIfNeeded() +{ + LoadTextIfNeeded(); + if (textDocument.location.editable) + return; - textLoaded = true; + ModuleTextDocument refreshed = ModuleTextStorage::LoadCommonForm(parent, guid, name); + if (refreshed.location.editable) + textDocument = refreshed; } String __fastcall TCommonForms::GetText() { LoadTextIfNeeded(); - return text; + return textDocument.text; } void __fastcall TCommonForms::SetText(String _text) { - text = _text; - textLoaded = true; + LoadTextIfNeeded(); + textDocument.text = _text; + textDocument.loaded = true; + textDocument.dirty = true; +} + +ModuleTextDocument& __fastcall TCommonForms::GetTextDocument() +{ + RefreshEditableTextIfNeeded(); + return textDocument; +} + +bool __fastcall TCommonForms::SaveTextToSource(const String& newText, String& errorText) +{ + RefreshEditableTextIfNeeded(); + return ModuleTextStorage::SaveDocument(textDocument, newText, errorText); +} + +bool __fastcall TCommonForms::HasEditableModuleText() +{ + RefreshEditableTextIfNeeded(); + return !textDocument.text.IsEmpty() || textDocument.location.editable; +} + +String __fastcall TCommonForms::GetEditableModuleText() +{ + return GetText(); +} + +void __fastcall TCommonForms::SetEditableModuleText(const String& value) +{ + SetText(value); +} + +bool __fastcall TCommonForms::SaveEditableModuleText(const String& value, String& errorText) +{ + return SaveTextToSource(value, errorText); +} + +ModuleTextLocation __fastcall TCommonForms::GetEditableModuleLocation() +{ + RefreshEditableTextIfNeeded(); + return textDocument.location; } std::vector>& TCommonForms::getAttributes() diff --git a/src/CommonForms.h b/src/CommonForms.h index 59886d6..82daa77 100644 --- a/src/CommonForms.h +++ b/src/CommonForms.h @@ -9,8 +9,7 @@ class TCommonForms : public BaseMetadataObject { private: - String text; - bool textLoaded; + ModuleTextDocument textDocument; public: __fastcall TCommonForms(); @@ -22,6 +21,13 @@ class TCommonForms : public BaseMetadataObject void __fastcall SetFormName(String _name); String __fastcall GetText(); void __fastcall SetText(String _text); + ModuleTextDocument& __fastcall GetTextDocument(); + bool __fastcall SaveTextToSource(const String& newText, String& errorText); + bool __fastcall HasEditableModuleText() override; + String __fastcall GetEditableModuleText() override; + void __fastcall SetEditableModuleText(const String& value) override; + bool __fastcall SaveEditableModuleText(const String& value, String& errorText) override; + ModuleTextLocation __fastcall GetEditableModuleLocation() override; std::vector>& getAttributes() override; std::vector>& getCommands() override; @@ -38,6 +44,7 @@ class TCommonForms : public BaseMetadataObject std::vector> tabularSections; std::vector> forms; void __fastcall LoadTextIfNeeded(); + void __fastcall RefreshEditableTextIfNeeded(); }; #endif diff --git a/src/CommonModules.cpp b/src/CommonModules.cpp index 30b01ce..cad31bf 100644 --- a/src/CommonModules.cpp +++ b/src/CommonModules.cpp @@ -357,73 +357,46 @@ namespace __fastcall TCommonModules::TCommonModules() : BaseMetadataObject() { root_data.reset(); - text = L""; - textLoaded = false; + textDocument.text = L""; + textDocument.loaded = false; + textDocument.dirty = false; } __fastcall TCommonModules::TCommonModules(v8catalog* _parent, const String& _guid) : BaseMetadataObject(_parent, _guid) { root_data.reset(); - text = L""; - textLoaded = false; + textDocument.text = L""; + textDocument.loaded = false; + textDocument.dirty = false; } __fastcall TCommonModules::TCommonModules(v8catalog* _parent, const String& _guid, const String& _name) : BaseMetadataObject(_parent, _guid, _name) { root_data.reset(); - text = L""; - textLoaded = false; + textDocument.text = L""; + textDocument.loaded = false; + textDocument.dirty = false; name = _name; } void __fastcall TCommonModules::LoadTextIfNeeded() { - if (textLoaded) + if (textDocument.loaded) return; - text = L""; - - if (!guid.IsEmpty()) - text = TryReadUnpackedModuleText(guid); - if (text.IsEmpty()) - text = TryReadUnpackedModuleTextByName(name); - - if ((parent) && (!guid.IsEmpty())) - { - std::vector candidates; - AddModuleFileCandidates(candidates, guid); - - try - { - v8file* objectFile = parent->GetFile(guid); - if (objectFile) - { - std::unique_ptr objectTree(get_treeFromV8file(objectFile)); - if (text.IsEmpty()) - text = FindEmbeddedModuleText(objectTree.get()); - - std::vector referencedGuids; - CollectGuidReferences(objectTree.get(), referencedGuids); - for (const auto& referencedGuid : referencedGuids) - { - if (text.IsEmpty()) - text = TryReadUnpackedModuleText(referencedGuid); - AddModuleFileCandidates(candidates, referencedGuid); - } - } - } - catch (...) - { - } + textDocument = ModuleTextStorage::LoadCommonModule(parent, guid, name); + textDocument.loaded = true; +} - for (size_t i = 0; i < candidates.size() && text.IsEmpty(); i++) - { - v8file* data_module = parent->GetFile(candidates[i]); - text = TryReadModuleContainer(data_module); - } - } +void __fastcall TCommonModules::RefreshEditableTextIfNeeded() +{ + LoadTextIfNeeded(); + if (textDocument.location.editable) + return; - textLoaded = true; + ModuleTextDocument refreshed = ModuleTextStorage::LoadCommonModule(parent, guid, name); + if (refreshed.location.editable) + textDocument = refreshed; } __fastcall TCommonModules::~TCommonModules() @@ -434,13 +407,54 @@ __fastcall TCommonModules::~TCommonModules() String __fastcall TCommonModules::GetText() { LoadTextIfNeeded(); - return text; + return textDocument.text; } void __fastcall TCommonModules::SetText(String _text) { - text = _text; - textLoaded = true; + LoadTextIfNeeded(); + textDocument.text = _text; + textDocument.loaded = true; + textDocument.dirty = true; +} + +ModuleTextDocument& __fastcall TCommonModules::GetTextDocument() +{ + RefreshEditableTextIfNeeded(); + return textDocument; +} + +bool __fastcall TCommonModules::SaveTextToSource(const String& newText, String& errorText) +{ + RefreshEditableTextIfNeeded(); + return ModuleTextStorage::SaveDocument(textDocument, newText, errorText); +} + +bool __fastcall TCommonModules::HasEditableModuleText() +{ + RefreshEditableTextIfNeeded(); + return !textDocument.text.IsEmpty() || textDocument.location.editable; +} + +String __fastcall TCommonModules::GetEditableModuleText() +{ + return GetText(); +} + +void __fastcall TCommonModules::SetEditableModuleText(const String& value) +{ + SetText(value); +} + +bool __fastcall TCommonModules::SaveEditableModuleText(const String& value, String& errorText) +{ + return SaveTextToSource(value, errorText); +} + +ModuleTextLocation __fastcall TCommonModules::GetEditableModuleLocation() +{ + RefreshEditableTextIfNeeded(); + return textDocument.location; } void __fastcall TCommonModules::initializeFromTree() diff --git a/src/CommonModules.h b/src/CommonModules.h index 6fd3361..da92efb 100644 --- a/src/CommonModules.h +++ b/src/CommonModules.h @@ -8,14 +8,14 @@ class TCommonModules : public BaseMetadataObject { private: - String text; - bool textLoaded; + ModuleTextDocument textDocument; std::vector> attributes; std::vector> comands; std::vector> moxels; std::vector> tabulars; std::vector> forms; void __fastcall LoadTextIfNeeded(); + void __fastcall RefreshEditableTextIfNeeded(); public: __fastcall TCommonModules(); @@ -24,6 +24,13 @@ class TCommonModules : public BaseMetadataObject virtual __fastcall ~TCommonModules(); String __fastcall GetText(); void __fastcall SetText(String _text); + ModuleTextDocument& __fastcall GetTextDocument(); + bool __fastcall SaveTextToSource(const String& newText, String& errorText); + bool __fastcall HasEditableModuleText() override; + String __fastcall GetEditableModuleText() override; + void __fastcall SetEditableModuleText(const String& value) override; + bool __fastcall SaveEditableModuleText(const String& value, String& errorText) override; + ModuleTextLocation __fastcall GetEditableModuleLocation() override; std::vector>& getAttributes() override { return attributes; } std::vector>& getCommands() override { return comands; } std::vector>& getLayouts() override { return moxels; } diff --git a/src/MainUnit.cpp b/src/MainUnit.cpp index e9ade36..e4d9c5f 100644 --- a/src/MainUnit.cpp +++ b/src/MainUnit.cpp @@ -829,7 +829,10 @@ void __fastcall TMainForm::CreateHighlightSettingsTab() //--------------------------------------------------------------------------- __fastcall TMainForm::TMainForm(TComponent* Owner) : TForm(Owner), HighlightSettingsLoading(false), - ModuleGeneralSyn(nullptr), ModuleSelectionTimer(nullptr), LastModuleNodeShown(nullptr), MDManager(std::make_unique()) + ModuleGeneralSyn(nullptr), ModuleSelectionTimer(nullptr), LastModuleNodeShown(nullptr), + CurrentModuleNode(nullptr), CurrentModuleObject(nullptr), LoadingModuleText(false), + CurrentModuleDirty(false), CurrentModuleOriginalText(L""), CurrentModuleKind(ModuleTextKind::Unknown), + CurrentModuleStandalone(false), MDManager(std::make_unique()) { VirtualStringTreeValue1C->NodeDataSize = sizeof(VirtualTreeData); Syn1CSyn = new TSyn1CSyn(this); @@ -846,6 +849,7 @@ __fastcall TMainForm::TMainForm(TComponent* Owner) : TForm(Owner), HighlightSett MemoManager->OnScanForFoldRanges = ModuleMemoScanForFoldRanges; MemoObject->UseCodeFolding = true; MemoManager->UseCodeFolding = true; + MemoObject->ReadOnly = false; VirtualStringTreeValue1C->OnClick = VirtualStringTreeValue1CClick; VirtualStringTreeValue1C->OnChange = VirtualStringTreeValue1CChange; VirtualStringTreeValue1C->OnNodeClick = VirtualStringTreeValue1CNodeClick; @@ -3089,6 +3093,133 @@ void __fastcall TMainForm::FormDestroy(TObject *Sender) } //--------------------------------------------------------------------------- +void __fastcall TMainForm::MemoObjectChange(TObject *Sender) +{ + if (LoadingModuleText || !MemoObject || !CurrentModuleObject) + return; + + CurrentModuleDirty = MemoObject->Lines->Text != CurrentModuleOriginalText; + + if (CurrentModuleNode) + { + VirtualTreeData* data = (VirtualTreeData*)VirtualStringTreeValue1C->GetNodeData(CurrentModuleNode); + if (data) + data->moduleDirty = CurrentModuleDirty; + } + + if (CurrentModuleDirty && mess) + mess->Status(L"Модуль изменен: " + ModuleTextStorage::DescribeLocation(CurrentModuleLocation)); +} +//--------------------------------------------------------------------------- + +void __fastcall TMainForm::ActionSaveModuleExecute(TObject *Sender) +{ + SaveCurrentModuleTextIfNeeded(false); +} +//--------------------------------------------------------------------------- + +void __fastcall TMainForm::FormCloseQuery(TObject *Sender, bool &CanClose) +{ + CanClose = SaveCurrentModuleTextIfNeeded(true); +} +//--------------------------------------------------------------------------- + +void __fastcall TMainForm::SetModuleEditorState(BaseMetadataObject* metadataObject, PVirtualNode node, const String& text, + ModuleTextKind kind) +{ + CurrentModuleNode = node; + CurrentModuleObject = metadataObject; + CurrentModuleDirty = false; + CurrentModuleOriginalText = text; + CurrentModuleKind = kind; + CurrentModuleStandalone = false; + CurrentStandaloneModuleDocument = ModuleTextDocument(); + CurrentModuleLocation = metadataObject + ? (kind == ModuleTextKind::Unknown + ? metadataObject->GetEditableModuleLocation() + : metadataObject->GetEditableModuleLocation(kind)) + : ModuleTextLocation(); + + if (MemoObject) + { + bool editable = metadataObject && CurrentModuleLocation.editable && FileExists(CurrentModuleLocation.filePath); + MemoObject->ReadOnly = !editable; + } +} +//--------------------------------------------------------------------------- + +bool __fastcall TMainForm::SaveCurrentModuleTextIfNeeded(bool forcePrompt) +{ + if ((!CurrentModuleObject && !CurrentModuleStandalone) || !CurrentModuleDirty) + return true; + + const String newText = MemoObject ? MemoObject->Lines->Text : L""; + + if (forcePrompt) + { + int answer = Application->MessageBox( + L"Текст текущего модуля изменен. Сохранить изменения в SourceCF?", + L"Сохранение модуля", + MB_YESNOCANCEL | MB_ICONQUESTION); + + if (answer == IDCANCEL) + return false; + + if (answer == IDNO) + { + CurrentModuleDirty = false; + if (CurrentModuleNode) + { + VirtualTreeData* data = (VirtualTreeData*)VirtualStringTreeValue1C->GetNodeData(CurrentModuleNode); + if (data) + data->moduleDirty = false; + } + return true; + } + } + + String errorText; + const bool saved = CurrentModuleStandalone + ? ModuleTextStorage::SaveDocument(CurrentStandaloneModuleDocument, newText, errorText) + : (CurrentModuleKind == ModuleTextKind::Unknown + ? CurrentModuleObject->SaveEditableModuleText(newText, errorText) + : CurrentModuleObject->SaveEditableModuleText(CurrentModuleKind, newText, errorText)); + + if (!saved) + { + Application->MessageBox( + (L"Не удалось сохранить модуль:\r\n" + errorText).c_str(), + L"Ошибка сохранения", + MB_OK | MB_ICONERROR); + return false; + } + + CurrentModuleOriginalText = newText; + CurrentModuleDirty = false; + + if (CurrentModuleNode) + { + VirtualTreeData* data = (VirtualTreeData*)VirtualStringTreeValue1C->GetNodeData(CurrentModuleNode); + if (data) + { + data->text_module = newText; + data->moduleDirty = false; + } + } + + if (mess) + mess->Status(L"Модуль сохранен: " + ModuleTextStorage::DescribeLocation(CurrentModuleLocation)); + + return true; +} +//--------------------------------------------------------------------------- + +bool __fastcall TMainForm::FlushCurrentModuleBeforeBuild() +{ + return SaveCurrentModuleTextIfNeeded(false); +} +//--------------------------------------------------------------------------- + void __fastcall TMainForm::ShowMetadataNodeText(PVirtualNode Node) { if (!MemoObject) @@ -3098,6 +3229,12 @@ void __fastcall TMainForm::ShowMetadataNodeText(PVirtualNode Node) if (!Node) return; + if (CurrentModuleNode && Node != CurrentModuleNode) + { + if (!SaveCurrentModuleTextIfNeeded(true)) + return; + } + VirtualTreeData* Data = (VirtualTreeData*)VirtualStringTreeValue1C->GetNodeData(Node); bool moduleTextSelected = false; String moduleText = L""; @@ -3105,11 +3242,31 @@ void __fastcall TMainForm::ShowMetadataNodeText(PVirtualNode Node) if (Data) { - if (!Data->text_module.IsEmpty()) + ModuleTextKind requestedKind = Data->moduleLocation.kind; + BaseMetadataObject* metadataObject = dynamic_cast(Data->MetadataObject); + bool nestedModuleRequested = metadataObject && !Data->moduleItemGuid.IsEmpty() + && (requestedKind == ModuleTextKind::FormModule || requestedKind == ModuleTextKind::CommandModule); + + if (nestedModuleRequested) + { + CurrentStandaloneModuleDocument = ModuleTextStorage::LoadByMetadataObject( + metadataObject->parent, Data->moduleItemGuid, Data->Name, requestedKind); + moduleText = CurrentStandaloneModuleDocument.text; + moduleTextSelected = !moduleText.IsEmpty() || CurrentStandaloneModuleDocument.location.editable; + Data->moduleLocation = CurrentStandaloneModuleDocument.location; + Data->moduleEditable = Data->moduleLocation.editable; + } + else if (!Data->text_module.IsEmpty()) { moduleText = Data->text_module; moduleTextSelected = true; } + else if (metadataObject && requestedKind != ModuleTextKind::Unknown && + metadataObject->HasEditableModuleText(requestedKind)) + { + moduleText = metadataObject->GetEditableModuleText(requestedKind); + moduleTextSelected = true; + } else { TCommonModules* module = dynamic_cast(Data->MetadataObject); @@ -3136,6 +3293,7 @@ void __fastcall TMainForm::ShowMetadataNodeText(PVirtualNode Node) } } + LoadingModuleText = true; MemoObject->BeginUpdate(); try { @@ -3144,6 +3302,7 @@ void __fastcall TMainForm::ShowMetadataNodeText(PVirtualNode Node) __finally { MemoObject->EndUpdate(); + LoadingModuleText = false; } MemoObject->CaretX = 1; MemoObject->CaretY = 1; @@ -3158,6 +3317,50 @@ void __fastcall TMainForm::ShowMetadataNodeText(PVirtualNode Node) mess->Status(L"Модуль: " + nodeName + L", длина текста: " + IntToStr(moduleText.Length())); } + BaseMetadataObject* editableObject = Data ? dynamic_cast(Data->MetadataObject) : nullptr; + ModuleTextKind editableKind = Data ? Data->moduleLocation.kind : ModuleTextKind::Unknown; + bool editableExists = false; + bool standaloneExists = Data && !Data->moduleItemGuid.IsEmpty() + && (editableKind == ModuleTextKind::FormModule || editableKind == ModuleTextKind::CommandModule) + && moduleTextSelected; + if (editableObject && !standaloneExists) + { + editableExists = editableKind == ModuleTextKind::Unknown + ? editableObject->HasEditableModuleText() + : editableObject->HasEditableModuleText(editableKind); + } + + if (editableObject && standaloneExists) + { + CurrentModuleNode = Node; + CurrentModuleObject = editableObject; + CurrentModuleDirty = false; + CurrentModuleOriginalText = moduleText; + CurrentModuleKind = editableKind; + CurrentModuleStandalone = true; + CurrentModuleLocation = CurrentStandaloneModuleDocument.location; + Data->moduleLocation = CurrentModuleLocation; + Data->moduleEditable = CurrentModuleLocation.editable; + if (MemoObject) + MemoObject->ReadOnly = !(CurrentModuleLocation.editable && FileExists(CurrentModuleLocation.filePath)); + if (mess && Data->moduleEditable) + mess->Status(L"Модуль открыт для редактирования: " + ModuleTextStorage::DescribeLocation(Data->moduleLocation)); + } + else if (editableObject && editableExists) + { + SetModuleEditorState(editableObject, Node, moduleText, editableKind); + Data->moduleLocation = editableKind == ModuleTextKind::Unknown + ? editableObject->GetEditableModuleLocation() + : editableObject->GetEditableModuleLocation(editableKind); + Data->moduleEditable = Data->moduleLocation.editable; + if (mess && Data->moduleEditable) + mess->Status(L"Модуль открыт для редактирования: " + ModuleTextStorage::DescribeLocation(Data->moduleLocation)); + } + else + { + SetModuleEditorState(nullptr, nullptr, L"", ModuleTextKind::Unknown); + } + if (moduleTextSelected && pagesEdit && TabModuleObject) pagesEdit->ActivePage = TabModuleObject; } @@ -3197,59 +3400,45 @@ void __fastcall TMainForm::VirtualStringTreeValue1CFocusChanged(TBaseVirtualTree } //--------------------------------------------------------------------------- +void __fastcall TMainForm::N4Click(TObject *Sender) +{ + Close(); +} +//--------------------------------------------------------------------------- +void __fastcall TMainForm::ActionSaveCFExecute(TObject *Sender) +{ + if (!FlushCurrentModuleBeforeBuild()) + return; + const String sourceCfDir = TPath::Combine(ExtractFilePath(ParamStr(0)), L"SourceCF"); + if (!TDirectory::Exists(sourceCfDir)) + { + Application->MessageBox( + L"Каталог SourceCF не найден. Откройте и распакуйте конфигурацию перед сборкой.", + L"Сборка cf", + MB_OK | MB_ICONWARNING); + return; + } + const String outFileName = TPath::Combine(ExtractFilePath(ParamStr(0)), L"1Cv8_out.cf"); + const std::string sourceCfDirStd = AnsiString(sourceCfDir).c_str(); + const std::string outFileNameStd = AnsiString(outFileName).c_str(); - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + int result = v8unpack::BuildCfFile(sourceCfDirStd, outFileNameStd, false); + if (result == v8unpack::V8UNPACK_OK) + { + if (mess) + mess->AddMessage(L"Файл конфигурации собран: " + outFileName, msSuccesfull); + } + else + { + Application->MessageBox( + (L"Ошибка сборки cf. Код: " + IntToStr(result)).c_str(), + L"Сборка cf", + MB_OK | MB_ICONERROR); + } +} +//--------------------------------------------------------------------------- diff --git a/src/MainUnit.dfm b/src/MainUnit.dfm index 7173d37..3fec930 100644 --- a/src/MainUnit.dfm +++ b/src/MainUnit.dfm @@ -11,6 +11,7 @@ object MainForm: TMainForm Font.Name = 'Segoe UI' Font.Style = [] Menu = MainMenu1 + OnCloseQuery = FormCloseQuery OnDestroy = FormDestroy TextHeight = 15 object split1: TSplitter @@ -196,6 +197,7 @@ object MainForm: TMainForm Highlighter = SynCppSyn1 WantReturns = False WantTabs = True + OnChange = MemoObjectChange FontSmoothing = fsmNone end end @@ -8855,6 +8857,15 @@ object MainForm: TMainForm Caption = #1054#1090#1082#1088#1099#1090#1100' '#1082#1086#1085#1092#1080#1075#1091#1088#1072#1094#1080#1102 OnExecute = ActionOpenCFExecute end + object ActionSaveCF: TAction + Caption = #1057#1086#1093#1088#1072#1085#1080#1090#1100' '#1082#1086#1085#1092#1080#1075#1091#1088#1072#1094#1080#1102 + OnExecute = ActionSaveCFExecute + end + object ActionSaveModule: TAction + Caption = #1057#1086#1093#1088#1072#1085#1080#1090#1100' '#1084#1086#1076#1091#1083#1100 + ShortCut = 16467 + OnExecute = ActionSaveModuleExecute + end end object MainMenu1: TMainMenu Left = 476 @@ -8864,6 +8875,19 @@ object MainForm: TMainForm object N2: TMenuItem Action = ActionOpenCF end + object N5: TMenuItem + Action = ActionSaveCF + end + object N6: TMenuItem + Action = ActionSaveModule + end + object N3: TMenuItem + Caption = '-' + end + object N4: TMenuItem + Caption = #1042#1099#1093#1086#1076 + OnClick = N4Click + end end end object SynCppSyn1: TSynCppSyn diff --git a/src/MainUnit.h b/src/MainUnit.h index 19aaefb..d3c0399 100644 --- a/src/MainUnit.h +++ b/src/MainUnit.h @@ -27,6 +27,7 @@ #include "SynHighlighterGeneral.hpp" #include "SynMemo.hpp" #include "SynHighlighter1C.h" +#include "ModuleTextStorage.h" #include @@ -36,6 +37,7 @@ #include "Tabular.h" #include "Form.h" #include "Enums.h" +#include "BaseMetadataObject.h" @@ -63,10 +65,14 @@ struct SubSys struct VirtualTreeData { String Name; - String text_module; - TObject* MetadataObject; - int Age; - int ImgIndex; + String text_module; + String moduleItemGuid; + TObject* MetadataObject = nullptr; + ModuleTextLocation moduleLocation; + bool moduleEditable = false; + bool moduleDirty = false; + int Age = 0; + int ImgIndex = 0; }; @@ -102,6 +108,12 @@ class TMainForm : public TForm TPanel *Panel1; TProgressBar *LoadProgressBar; TSynCppSyn *SynCppSyn1; + TMenuItem *N3; + TMenuItem *N4; + TMenuItem *N5; + TAction *ActionSaveCF; + TAction *ActionSaveModule; + TMenuItem *N6; void __fastcall btnOpenEditNameClick(TObject *Sender); void __fastcall btnGOClick(TObject *Sender); void __fastcall VirtualStringTreeValue1CInitNode(TBaseVirtualTree *Sender, PVirtualNode ParentNode, @@ -123,6 +135,11 @@ class TMainForm : public TForm void __fastcall ModuleMemoScanForFoldRanges(TObject *Sender, TSynFoldRanges *FoldRanges, TStrings *LinesToScan, int FromLine, int ToLine); void __fastcall ModuleSelectionTimerTimer(TObject *Sender); + void __fastcall N4Click(TObject *Sender); + void __fastcall ActionSaveCFExecute(TObject *Sender); + void __fastcall MemoObjectChange(TObject *Sender); + void __fastcall ActionSaveModuleExecute(TObject *Sender); + void __fastcall FormCloseQuery(TObject *Sender, bool &CanClose); private: // User declarations @@ -145,6 +162,15 @@ class TMainForm : public TForm bool HighlightSettingsLoading; TTimer *ModuleSelectionTimer; PVirtualNode LastModuleNodeShown; + PVirtualNode CurrentModuleNode; + BaseMetadataObject* CurrentModuleObject; + bool LoadingModuleText; + bool CurrentModuleDirty; + String CurrentModuleOriginalText; + ModuleTextLocation CurrentModuleLocation; + ModuleTextKind CurrentModuleKind; + ModuleTextDocument CurrentStandaloneModuleDocument; + bool CurrentModuleStandalone; std::unique_ptr MDManager; // Умный указатель для автоматического управления памятью void __fastcall CreateHighlightSettingsTab(); void __fastcall ApplyHighlightSettings(); @@ -154,6 +180,9 @@ class TMainForm : public TForm void __fastcall HighlightSettingsChanged(TObject *Sender); void __fastcall ResetHighlightSettingsClick(TObject *Sender); void __fastcall ShowMetadataNodeText(PVirtualNode Node); + bool __fastcall SaveCurrentModuleTextIfNeeded(bool forcePrompt); + bool __fastcall FlushCurrentModuleBeforeBuild(); + void __fastcall SetModuleEditorState(BaseMetadataObject* metadataObject, PVirtualNode node, const String& text, ModuleTextKind kind); public: // User declarations __fastcall TMainForm(TComponent* Owner); void __fastcall ResetLoadProgress(int maxValue, const String& statusText = L""); diff --git a/src/MetadataObjectInformationRegister.cpp b/src/MetadataObjectInformationRegister.cpp index f02926e..5830898 100644 --- a/src/MetadataObjectInformationRegister.cpp +++ b/src/MetadataObjectInformationRegister.cpp @@ -8,6 +8,28 @@ //--------------------------------------------------------------------------- #pragma package(smart_init) +namespace +{ + String FindFirstGuid(tree* node) + { + if (!node) + return L""; + + String value = Trim(node->get_value()); + if (ModuleTextStorage::IsGuidLike(value)) + return value; + + for (int i = 0; i < node->get_num_subnode(); i++) + { + String found = FindFirstGuid(node->get_subnode(i)); + if (!found.IsEmpty()) + return found; + } + + return L""; + } +} + __fastcall MetadataObjectInformationRegister::MetadataObjectInformationRegister() : BaseMetadataObject() { @@ -98,7 +120,7 @@ void MetadataObjectInformationRegister::initializeFromTreeWithPaths(const InfoRe { String guid_md = curNodeChild->get_value(); String NameForm = paths.getFormNameFunc(parent, guid_md); - forms.push_back(std::make_unique(NameForm, "")); + forms.push_back(std::make_unique(NameForm, guid_md)); } } @@ -110,11 +132,13 @@ void MetadataObjectInformationRegister::initializeFromTreeWithPaths(const InfoRe int DeltaCom = CountCom - 2; for (int i = 0; i < CountCom; i++) { - tree* itemNode = &(*root_data.get())[0][paths.cmdIdx][i + CountCom - DeltaCom]; + tree* commandNode = &(*root_data.get())[0][paths.cmdIdx][i + CountCom - DeltaCom]; + String commandGuid = FindFirstGuid(commandNode); + tree* itemNode = commandNode; for (size_t p = 0; p < paths.cmdItemPath.size(); p++) itemNode = &(*itemNode)[paths.cmdItemPath[p]]; String NameCom = itemNode->get_value(); - comands.push_back(std::make_unique(NameCom, "")); + comands.push_back(std::make_unique(NameCom, commandGuid)); } // Макеты diff --git a/src/MetadataObjectWithSections.cpp b/src/MetadataObjectWithSections.cpp index 363e07e..7b8147e 100644 --- a/src/MetadataObjectWithSections.cpp +++ b/src/MetadataObjectWithSections.cpp @@ -10,6 +10,32 @@ namespace { + bool SupportsMetadataObjectModuleKind(ModuleTextKind kind) + { + return kind == ModuleTextKind::Unknown + || kind == ModuleTextKind::ObjectModule + || kind == ModuleTextKind::ManagerModule; + } + + String FindFirstGuid(tree* node) + { + if (!node) + return L""; + + String value = Trim(node->get_value()); + if (ModuleTextStorage::IsGuidLike(value)) + return value; + + for (int i = 0; i < node->get_num_subnode(); i++) + { + String found = FindFirstGuid(node->get_subnode(i)); + if (!found.IsEmpty()) + return found; + } + + return L""; + } + tree* GetNodeByPath(tree* root, std::initializer_list indexes) { tree* current = root; @@ -48,16 +74,22 @@ namespace __fastcall MetadataObjectWithSections::MetadataObjectWithSections() : BaseMetadataObject() { + objectModuleDocument.loaded = false; + managerModuleDocument.loaded = false; } __fastcall MetadataObjectWithSections::MetadataObjectWithSections(v8catalog* _parent, const String& _guid) : BaseMetadataObject(_parent, _guid) { + objectModuleDocument.loaded = false; + managerModuleDocument.loaded = false; } __fastcall MetadataObjectWithSections::MetadataObjectWithSections(v8catalog* _parent, const String& _guid, const String& _name) : BaseMetadataObject(_parent, _guid, _name) { + objectModuleDocument.loaded = false; + managerModuleDocument.loaded = false; } __fastcall MetadataObjectWithSections::~MetadataObjectWithSections() @@ -133,7 +165,7 @@ void MetadataObjectWithSections::initializeFromTreeWithPaths(const MetadataTreeP { String guid_md = curNodeChild->get_value(); String NameForm = paths.getFormNameFunc(parent, guid_md); - forms.push_back(std::make_unique(NameForm, "")); + forms.push_back(std::make_unique(NameForm, guid_md)); } catch (...) { @@ -150,12 +182,13 @@ void MetadataObjectWithSections::initializeFromTreeWithPaths(const MetadataTreeP { try { - tree* itemNode = GetNodeByPath(root_data.get(), {0, paths.cmdIdx, i + CountCom - DeltaCom}); - itemNode = GetNodeByPath(itemNode, paths.cmdItemPath); + tree* commandNode = GetNodeByPath(root_data.get(), {0, paths.cmdIdx, i + CountCom - DeltaCom}); + String commandGuid = FindFirstGuid(commandNode); + tree* itemNode = GetNodeByPath(commandNode, paths.cmdItemPath); if (!itemNode) continue; String NameCom = itemNode->get_value(); - comands.push_back(std::make_unique(NameCom, "")); + comands.push_back(std::make_unique(NameCom, commandGuid)); } catch (...) { @@ -180,5 +213,104 @@ void MetadataObjectWithSections::initializeFromTreeWithPaths(const MetadataTreeP { } } - } + } +} + +ModuleTextDocument& __fastcall MetadataObjectWithSections::GetModuleDocument(ModuleTextKind kind) +{ + return kind == ModuleTextKind::ManagerModule ? managerModuleDocument : objectModuleDocument; +} + +void __fastcall MetadataObjectWithSections::RefreshModuleDocument(ModuleTextKind kind) +{ + if (!SupportsMetadataObjectModuleKind(kind)) + return; + + ModuleTextKind effectiveKind = kind == ModuleTextKind::Unknown ? ModuleTextKind::ObjectModule : kind; + ModuleTextDocument& document = GetModuleDocument(effectiveKind); + + if (document.loaded && document.location.editable) + return; + + ModuleTextDocument refreshed = ModuleTextStorage::LoadByMetadataObject(parent, guid, name, effectiveKind); + if (!document.loaded || refreshed.location.editable) + document = refreshed; + document.loaded = true; +} + +bool __fastcall MetadataObjectWithSections::HasEditableModuleText() +{ + return HasEditableModuleText(ModuleTextKind::ObjectModule); +} + +String __fastcall MetadataObjectWithSections::GetEditableModuleText() +{ + return GetEditableModuleText(ModuleTextKind::ObjectModule); +} + +void __fastcall MetadataObjectWithSections::SetEditableModuleText(const String& value) +{ + SetEditableModuleText(ModuleTextKind::ObjectModule, value); +} + +bool __fastcall MetadataObjectWithSections::SaveEditableModuleText(const String& value, String& errorText) +{ + return SaveEditableModuleText(ModuleTextKind::ObjectModule, value, errorText); +} + +ModuleTextLocation __fastcall MetadataObjectWithSections::GetEditableModuleLocation() +{ + return GetEditableModuleLocation(ModuleTextKind::ObjectModule); +} + +bool __fastcall MetadataObjectWithSections::HasEditableModuleText(ModuleTextKind kind) +{ + if (!SupportsMetadataObjectModuleKind(kind)) + return false; + + RefreshModuleDocument(kind); + ModuleTextDocument& document = GetModuleDocument(kind); + return !document.text.IsEmpty() || document.location.editable; +} + +String __fastcall MetadataObjectWithSections::GetEditableModuleText(ModuleTextKind kind) +{ + if (!SupportsMetadataObjectModuleKind(kind)) + return L""; + + RefreshModuleDocument(kind); + return GetModuleDocument(kind).text; +} + +void __fastcall MetadataObjectWithSections::SetEditableModuleText(ModuleTextKind kind, const String& value) +{ + if (!SupportsMetadataObjectModuleKind(kind)) + return; + + RefreshModuleDocument(kind); + ModuleTextDocument& document = GetModuleDocument(kind); + document.text = value; + document.dirty = true; + document.loaded = true; +} + +bool __fastcall MetadataObjectWithSections::SaveEditableModuleText(ModuleTextKind kind, const String& value, String& errorText) +{ + if (!SupportsMetadataObjectModuleKind(kind)) + { + errorText = L"Неподдерживаемый вид модуля для объекта метаданных."; + return false; + } + + RefreshModuleDocument(kind); + return ModuleTextStorage::SaveDocument(GetModuleDocument(kind), value, errorText); +} + +ModuleTextLocation __fastcall MetadataObjectWithSections::GetEditableModuleLocation(ModuleTextKind kind) +{ + if (!SupportsMetadataObjectModuleKind(kind)) + return ModuleTextLocation(); + + RefreshModuleDocument(kind); + return GetModuleDocument(kind).location; } diff --git a/src/MetadataObjectWithSections.h b/src/MetadataObjectWithSections.h index c671e06..95c96d9 100644 --- a/src/MetadataObjectWithSections.h +++ b/src/MetadataObjectWithSections.h @@ -34,8 +34,12 @@ class MetadataObjectWithSections : public BaseMetadataObject std::vector> moxels; std::vector> tabulars; std::vector> forms; + ModuleTextDocument objectModuleDocument; + ModuleTextDocument managerModuleDocument; void initializeFromTreeWithPaths(const MetadataTreePaths& paths); + void __fastcall RefreshModuleDocument(ModuleTextKind kind); + ModuleTextDocument& __fastcall GetModuleDocument(ModuleTextKind kind); public: __fastcall MetadataObjectWithSections(); @@ -48,6 +52,17 @@ class MetadataObjectWithSections : public BaseMetadataObject std::vector>& getLayouts() override { return moxels; } std::vector>& getTabularSections() override { return tabulars; } std::vector>& getForms() override { return forms; } + + bool __fastcall HasEditableModuleText() override; + String __fastcall GetEditableModuleText() override; + void __fastcall SetEditableModuleText(const String& value) override; + bool __fastcall SaveEditableModuleText(const String& value, String& errorText) override; + ModuleTextLocation __fastcall GetEditableModuleLocation() override; + bool __fastcall HasEditableModuleText(ModuleTextKind kind) override; + String __fastcall GetEditableModuleText(ModuleTextKind kind) override; + void __fastcall SetEditableModuleText(ModuleTextKind kind, const String& value) override; + bool __fastcall SaveEditableModuleText(ModuleTextKind kind, const String& value, String& errorText) override; + ModuleTextLocation __fastcall GetEditableModuleLocation(ModuleTextKind kind) override; }; //--------------------------------------------------------------------------- diff --git a/src/MetadataTreeBuilder.cpp b/src/MetadataTreeBuilder.cpp index 2167354..49fab3a 100644 --- a/src/MetadataTreeBuilder.cpp +++ b/src/MetadataTreeBuilder.cpp @@ -15,7 +15,11 @@ void initNode(VirtualTreeData* data, const String& name, int imageIndex, int age data->Age = age; data->ImgIndex = imageIndex; data->text_module = L""; + data->moduleItemGuid = L""; data->MetadataObject = nullptr; + data->moduleLocation = ModuleTextLocation(); + data->moduleEditable = false; + data->moduleDirty = false; } PVirtualNode addChildNode(TVirtualStringTree* tree, PVirtualNode parent, const String& name, int imageIndex, int age) @@ -26,8 +30,7 @@ PVirtualNode addChildNode(TVirtualStringTree* tree, PVirtualNode parent, const S return childNode; } -void addTabularSections(TVirtualStringTree* tree, PVirtualNode parent, - const std::vector>& items, int age) +void addTabularSections(TVirtualStringTree* tree, PVirtualNode parent, const std::vector>& items, int age) { PVirtualNode sectionNode = addChildNode(tree, parent, L"Табличные части", TreeImage::TabularSections, age); tree->Expanded[sectionNode] = true; @@ -39,56 +42,89 @@ void addTabularSections(TVirtualStringTree* tree, PVirtualNode parent, { for (const auto& attribute : item->attributes) addChildNode(tree, tabularNode, attribute->name, TreeImage::Attributes, age); + tree->Expanded[tabularNode] = true; } } } +static void addModuleTextNode(TVirtualStringTree* tree, PVirtualNode parent, BaseMetadataObject* metadataObject, const String& name, ModuleTextKind kind, int imageIndex) +{ + if (!tree || !metadataObject) + return; + + PVirtualNode moduleNode = addChildNode(tree, parent, name, imageIndex); + VirtualTreeData* moduleData = static_cast(tree->GetNodeData(moduleNode)); + moduleData->MetadataObject = metadataObject; + moduleData->moduleLocation.kind = kind; + moduleData->moduleEditable = false; +} + +static void addNestedModuleTextNode(TVirtualStringTree* tree, PVirtualNode parent, BaseMetadataObject* metadataObject, const String& itemGuid, const String& name, ModuleTextKind kind, int imageIndex) +{ + if (!tree || !metadataObject || itemGuid.IsEmpty()) + return; + + PVirtualNode moduleNode = addChildNode(tree, parent, name, imageIndex); + VirtualTreeData* moduleData = static_cast(tree->GetNodeData(moduleNode)); + moduleData->MetadataObject = metadataObject; + moduleData->moduleItemGuid = itemGuid; + moduleData->moduleLocation.kind = kind; + moduleData->moduleEditable = false; +} + +template +static void addMetadataItemSection(TVirtualStringTree* tree, PVirtualNode parent, const String& sectionName, int sectionImageIndex, int itemImageIndex, const Collection& items, BaseMetadataObject* metadataObject, ModuleTextKind moduleKind, const String& moduleNodeName) +{ + PVirtualNode sectionNode = addChildNode(tree, parent, sectionName, sectionImageIndex); + for (const auto& item : items) + { + PVirtualNode itemNode = addChildNode(tree, sectionNode, item->name, itemImageIndex); + addNestedModuleTextNode(tree, itemNode, metadataObject, item->guid, moduleNodeName, moduleKind, itemImageIndex); + } +} + //--------------------------------------------------------------------------- // Секции для стандартных объектов и регистров -void fillStandardMetadataSections(TVirtualStringTree* tree, PVirtualNode childNode, - BaseMetadataObject* metadataObject) +void fillStandardMetadataSections(TVirtualStringTree* tree, PVirtualNode childNode, BaseMetadataObject* metadataObject) { if (!metadataObject) return; - addSection(tree, childNode, L"Реквизиты", TreeImage::Attributes, TreeImage::Attributes, - metadataObject->getAttributes(), [](const auto& item) { return item->name; }); + addModuleTextNode(tree, childNode, metadataObject, L"Модуль объекта", ModuleTextKind::ObjectModule, TreeImage::Forms); + addModuleTextNode(tree, childNode, metadataObject, L"Модуль менеджера", ModuleTextKind::ManagerModule, TreeImage::Forms); + + addSection(tree, childNode, L"Реквизиты", TreeImage::Attributes, TreeImage::Attributes, metadataObject->getAttributes(), [](const auto& item) { return item->name; }); + addTabularSections(tree, childNode, metadataObject->getTabularSections(), 0); - addSection(tree, childNode, L"Формы", TreeImage::Forms, TreeImage::Forms, - metadataObject->getForms(), [](const auto& item) { return item->name; }); - addSection(tree, childNode, L"Команды", TreeImage::Commands, TreeImage::Commands, - metadataObject->getCommands(), [](const auto& item) { return item->name; }); - addSection(tree, childNode, L"Макеты", TreeImage::Layouts, TreeImage::Layouts, - metadataObject->getLayouts(), [](const auto& item) { return item->name; }); + addMetadataItemSection(tree, childNode, L"Формы", TreeImage::Forms, TreeImage::Forms, metadataObject->getForms(), metadataObject, ModuleTextKind::FormModule, L"Модуль формы"); + addMetadataItemSection(tree, childNode, L"Команды", TreeImage::Commands, TreeImage::Commands, metadataObject->getCommands(), metadataObject, ModuleTextKind::CommandModule, L"Модуль команды"); + addSection(tree, childNode, L"Макеты", TreeImage::Layouts, TreeImage::Layouts, metadataObject->getLayouts(), [](const auto& item) { return item->name; }); } -void fillInformationRegisterSections(TVirtualStringTree* tree, PVirtualNode childNode, - MetadataObjectInformationRegister* metadataObject) +void fillInformationRegisterSections(TVirtualStringTree* tree, PVirtualNode childNode, MetadataObjectInformationRegister* metadataObject) { if (!metadataObject) return; - addSection(tree, childNode, L"Измерения", TreeImage::Dimensions, TreeImage::Dimensions, - metadataObject->getDimensions(), [](const auto& item) { return item->name; }); - addSection(tree, childNode, L"Ресурсы", TreeImage::Resources, TreeImage::Resources, - metadataObject->getResources(), [](const auto& item) { return item->name; }); - addSection(tree, childNode, L"Реквизиты", TreeImage::Attributes, TreeImage::Attributes, - metadataObject->getAttributes(), [](const auto& item) { return item->name; }); - addSection(tree, childNode, L"Формы", TreeImage::Forms, TreeImage::Forms, - metadataObject->getForms(), [](const auto& item) { return item->name; }); - addSection(tree, childNode, L"Команды", TreeImage::Commands, TreeImage::Commands, - metadataObject->getCommands(), [](const auto& item) { return item->name; }); - addSection(tree, childNode, L"Макеты", TreeImage::Layouts, TreeImage::Layouts, - metadataObject->getLayouts(), [](const auto& item) { return item->name; }); + addModuleTextNode(tree, childNode, metadataObject, L"Модуль объекта", ModuleTextKind::ObjectModule, TreeImage::Forms); + addModuleTextNode(tree, childNode, metadataObject, L"Модуль менеджера", ModuleTextKind::ManagerModule, TreeImage::Forms); + + addSection(tree, childNode, L"Измерения", TreeImage::Dimensions, TreeImage::Dimensions, metadataObject->getDimensions(), [](const auto& item) { return item->name; }); + addSection(tree, childNode, L"Ресурсы", TreeImage::Resources, TreeImage::Resources, metadataObject->getResources(), [](const auto& item) { return item->name; }); + addSection(tree, childNode, L"Реквизиты", TreeImage::Attributes, TreeImage::Attributes, metadataObject->getAttributes(), [](const auto& item) { return item->name; }); + + addMetadataItemSection(tree, childNode, L"Формы", TreeImage::Forms, TreeImage::Forms, metadataObject->getForms(), metadataObject, ModuleTextKind::FormModule, L"Модуль формы"); + addMetadataItemSection(tree, childNode, L"Команды", TreeImage::Commands, TreeImage::Commands, metadataObject->getCommands(), metadataObject, ModuleTextKind::CommandModule, L"Модуль команды"); + + addSection(tree, childNode, L"Макеты", TreeImage::Layouts, TreeImage::Layouts, metadataObject->getLayouts(), [](const auto& item) { return item->name; }); } //--------------------------------------------------------------------------- // fill*Tree -void fillCatalogsTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, - int imgIndex, BaseMetadataObject* metadataObject) +void fillCatalogsTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, int imgIndex, BaseMetadataObject* metadataObject) { if (!metadataObject) return; @@ -97,164 +133,159 @@ void fillCatalogsTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualT fillStandardMetadataSections(tree, childNode, metadataObject); } -void fillFormsCommandsTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, - int imgIndex, BaseMetadataObject* metadataObject) +void fillFormsCommandsTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, int imgIndex, BaseMetadataObject* metadataObject) { if (!metadataObject) return; + initNode(childData, metadataObject->name, imgIndex); + childData->MetadataObject = metadataObject; - addSection(tree, childNode, L"Формы", TreeImage::Forms, TreeImage::Forms, - metadataObject->getForms(), [](const auto& item) { return item->name; }); - addSection(tree, childNode, L"Команды", TreeImage::Commands, TreeImage::Commands, - metadataObject->getCommands(), [](const auto& item) { return item->name; }); + + addMetadataItemSection(tree, childNode, L"Формы", TreeImage::Forms, TreeImage::Forms, metadataObject->getForms(), metadataObject, ModuleTextKind::FormModule, L"Модуль формы"); + addMetadataItemSection(tree, childNode, L"Команды", TreeImage::Commands, TreeImage::Commands, metadataObject->getCommands(), metadataObject, ModuleTextKind::CommandModule, L"Модуль команды"); } -void fillAccumulationRegisterTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, - int imgIndex, MetadataObjectInformationRegister* metadataObject) +void fillAccumulationRegisterTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, int imgIndex, MetadataObjectInformationRegister* metadataObject) { if (!metadataObject) return; + initNode(childData, metadataObject->name, imgIndex); + childData->MetadataObject = metadataObject; + fillInformationRegisterSections(tree, childNode, metadataObject); } -void fillCalculationRegisterTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, - int imgIndex, MetadataObjectInformationRegister* metadataObject) +void fillCalculationRegisterTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, int imgIndex, MetadataObjectInformationRegister* metadataObject) { if (!metadataObject) return; + initNode(childData, metadataObject->name, imgIndex); + childData->MetadataObject = metadataObject; + fillInformationRegisterSections(tree, childNode, metadataObject); } -void fillInformationRegisterTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, - int imgIndex, MetadataObjectInformationRegister* metadataObject) +void fillInformationRegisterTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, int imgIndex, MetadataObjectInformationRegister* metadataObject) { if (!metadataObject) return; + initNode(childData, metadataObject->name, imgIndex); + childData->MetadataObject = metadataObject; + fillInformationRegisterSections(tree, childNode, metadataObject); } -void fillAccountingRegisterTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, - int imgIndex, TAccountingRegisters* metadataObject) +void fillAccountingRegisterTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, int imgIndex, TAccountingRegisters* metadataObject) { if (!metadataObject) return; + initNode(childData, metadataObject->name, imgIndex); + childData->MetadataObject = metadataObject; - addSection(tree, childNode, L"Измерения", TreeImage::Dimensions, TreeImage::Dimensions, - metadataObject->getDimensions(), [](const auto& item) { return item->name; }); - addSection(tree, childNode, L"Ресурсы", TreeImage::Resources, TreeImage::Resources, - metadataObject->getResources(), [](const auto& item) { return item->name; }); - addSection(tree, childNode, L"Реквизиты", TreeImage::Attributes, TreeImage::Attributes, - metadataObject->getAttributes(), [](const auto& item) { return item->name; }); - addSection(tree, childNode, L"Признаки учета", TreeImage::AccountingFlags, TreeImage::AccountingFlags, - metadataObject->getAccountingFlags(), [](const auto& item) { return item->name; }); - addSection(tree, childNode, L"Признаки учета субконто", TreeImage::SubcontoFlags, TreeImage::SubcontoFlags, - metadataObject->getDimensionAccountingFlags(), [](const auto& item) { return item->name; }); - addSection(tree, childNode, L"Формы", TreeImage::Forms, TreeImage::Forms, - metadataObject->getForms(), [](const auto& item) { return item->name; }); - addSection(tree, childNode, L"Команды", TreeImage::Commands, TreeImage::Commands, - metadataObject->getCommands(), [](const auto& item) { return item->name; }); - addSection(tree, childNode, L"Макеты", TreeImage::Layouts, TreeImage::Layouts, - metadataObject->getLayouts(), [](const auto& item) { return item->name; }); + addSection(tree, childNode, L"Измерения", TreeImage::Dimensions, TreeImage::Dimensions, metadataObject->getDimensions(), [](const auto& item) { return item->name; }); + addSection(tree, childNode, L"Ресурсы", TreeImage::Resources, TreeImage::Resources, metadataObject->getResources(), [](const auto& item) { return item->name; }); + addSection(tree, childNode, L"Реквизиты", TreeImage::Attributes, TreeImage::Attributes, metadataObject->getAttributes(), [](const auto& item) { return item->name; }); + addSection(tree, childNode, L"Признаки учета", TreeImage::AccountingFlags, TreeImage::AccountingFlags, metadataObject->getAccountingFlags(), [](const auto& item) { return item->name; }); + addSection(tree, childNode, L"Признаки учета субконто", TreeImage::SubcontoFlags, TreeImage::SubcontoFlags, metadataObject->getDimensionAccountingFlags(), [](const auto& item) { return item->name; }); + + addMetadataItemSection(tree, childNode, L"Формы", TreeImage::Forms, TreeImage::Forms, metadataObject->getForms(), metadataObject, ModuleTextKind::FormModule, L"Модуль формы"); + addMetadataItemSection(tree, childNode, L"Команды", TreeImage::Commands, TreeImage::Commands, metadataObject->getCommands(), metadataObject, ModuleTextKind::CommandModule, L"Модуль команды"); + + addSection(tree, childNode, L"Макеты", TreeImage::Layouts, TreeImage::Layouts, metadataObject->getLayouts(), [](const auto& item) { return item->name; }); } -void fillChartAccTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, - int imgIndex, TChartOfAccounts* metadataObject) +void fillChartAccTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, int imgIndex, TChartOfAccounts* metadataObject) { if (!metadataObject) return; + initNode(childData, metadataObject->name, imgIndex); + childData->MetadataObject = metadataObject; - addSection(tree, childNode, L"Реквизиты", TreeImage::Attributes, TreeImage::Attributes, - metadataObject->getAttributes(), [](const auto& item) { return item->name; }); - addSection(tree, childNode, L"Признаки учета", TreeImage::AccountingFlags, TreeImage::AccountingFlags, - metadataObject->accflags, [](const auto& item) { return item->name; }); - addSection(tree, childNode, L"Признаки учета субконто", TreeImage::SubcontoFlags, TreeImage::SubcontoFlags, - metadataObject->dimaccflags, [](const auto& item) { return item->name; }); + addSection(tree, childNode, L"Реквизиты", TreeImage::Attributes, TreeImage::Attributes, metadataObject->getAttributes(), [](const auto& item) { return item->name; }); + addSection(tree, childNode, L"Признаки учета", TreeImage::AccountingFlags, TreeImage::AccountingFlags, metadataObject->accflags, [](const auto& item) { return item->name; }); + addSection(tree, childNode, L"Признаки учета субконто", TreeImage::SubcontoFlags, TreeImage::SubcontoFlags, metadataObject->dimaccflags, [](const auto& item) { return item->name; }); + addTabularSections(tree, childNode, metadataObject->getTabularSections(), 0); - addSection(tree, childNode, L"Формы", TreeImage::Forms, TreeImage::Forms, - metadataObject->getForms(), [](const auto& item) { return item->name; }); - addSection(tree, childNode, L"Команды", TreeImage::Commands, TreeImage::Commands, - metadataObject->getCommands(), [](const auto& item) { return item->name; }); - addSection(tree, childNode, L"Макеты", TreeImage::Layouts, TreeImage::Layouts, - metadataObject->getLayouts(), [](const auto& item) { return item->name; }); + + addMetadataItemSection(tree, childNode, L"Формы", TreeImage::Forms, TreeImage::Forms, metadataObject->getForms(), metadataObject, ModuleTextKind::FormModule, L"Модуль формы"); + addMetadataItemSection(tree, childNode, L"Команды", TreeImage::Commands, TreeImage::Commands, metadataObject->getCommands(), metadataObject, ModuleTextKind::CommandModule, L"Модуль команды"); + + addSection(tree, childNode, L"Макеты", TreeImage::Layouts, TreeImage::Layouts, metadataObject->getLayouts(), [](const auto& item) { return item->name; }); } -void fillJournalTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, - int imgIndex, BaseMetadataObject* metadataObject) +void fillJournalTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, int imgIndex, BaseMetadataObject* metadataObject) { if (!metadataObject) return; + initNode(childData, metadataObject->name, imgIndex); + childData->MetadataObject = metadataObject; - addSection(tree, childNode, L"Графы", TreeImage::JournalColumns, TreeImage::JournalColumns, - metadataObject->getAttributes(), [](const auto& item) { return item->name; }); + addSection(tree, childNode, L"Графы", TreeImage::JournalColumns, TreeImage::JournalColumns, metadataObject->getAttributes(), [](const auto& item) { return item->name; }); + addTabularSections(tree, childNode, metadataObject->getTabularSections()); - addSection(tree, childNode, L"Формы", TreeImage::Forms, TreeImage::Forms, - metadataObject->getForms(), [](const auto& item) { return item->name; }); - addSection(tree, childNode, L"Команды", TreeImage::Commands, TreeImage::Commands, - metadataObject->getCommands(), [](const auto& item) { return item->name; }); - addSection(tree, childNode, L"Макеты", TreeImage::Layouts, TreeImage::Layouts, - metadataObject->getLayouts(), [](const auto& item) { return item->name; }); + + addMetadataItemSection(tree, childNode, L"Формы", TreeImage::Forms, TreeImage::Forms, metadataObject->getForms(), metadataObject, ModuleTextKind::FormModule, L"Модуль формы"); + addMetadataItemSection(tree, childNode, L"Команды", TreeImage::Commands, TreeImage::Commands, metadataObject->getCommands(), metadataObject, ModuleTextKind::CommandModule, L"Модуль команды"); + addSection(tree, childNode, L"Макеты", TreeImage::Layouts, TreeImage::Layouts, metadataObject->getLayouts(), [](const auto& item) { return item->name; }); } -void fillEnumTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, - int imgIndex, TEnums* curEnum) +void fillEnumTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, int imgIndex, TEnums* curEnum) { initNode(childData, curEnum->name, imgIndex); + childData->MetadataObject = curEnum; - addSection(tree, childNode, L"Значения", TreeImage::Attributes, TreeImage::Attributes, - curEnum->attributes, [](const auto& item) { return item; }); - addSection(tree, childNode, L"Формы", TreeImage::Forms, TreeImage::Forms, - curEnum->forms, [](const auto& item) { return item; }); - addSection(tree, childNode, L"Команды", TreeImage::Commands, TreeImage::Commands, - curEnum->comands, [](const auto& item) { return item; }); - addSection(tree, childNode, L"Макеты", TreeImage::Layouts, TreeImage::Layouts, - curEnum->moxels, [](const auto& item) { return item; }); + addSection(tree, childNode, L"Значения", TreeImage::Attributes, TreeImage::Attributes, curEnum->attributes, [](const auto& item) { return item; }); + addSection(tree, childNode, L"Формы", TreeImage::Forms, TreeImage::Forms, curEnum->forms, [](const auto& item) { return item; }); + addSection(tree, childNode, L"Команды", TreeImage::Commands, TreeImage::Commands, curEnum->comands, [](const auto& item) { return item; }); + addSection(tree, childNode, L"Макеты", TreeImage::Layouts, TreeImage::Layouts, curEnum->moxels, [](const auto& item) { return item; }); } namespace { - void addStringSection(TVirtualStringTree* tree, PVirtualNode parent, - const String& sectionName, int sectionImageIndex, int itemImageIndex, - const std::vector& items) + void addStringSection(TVirtualStringTree* tree, PVirtualNode parent, const String& sectionName, int sectionImageIndex, int itemImageIndex, const std::vector& items) { PVirtualNode sectionNode = nullptr; for (const auto& item : items) { if (!item.IsEmpty()) { + if (!sectionNode) sectionNode = addChildNode(tree, parent, sectionName, sectionImageIndex); + addChildNode(tree, sectionNode, item, itemImageIndex); + } } } - void fillExternalTableTree(TVirtualStringTree* tree, PVirtualNode parent, - const TExternalDataSourceTable& tableData, int imgIndex) + void fillExternalTableTree(TVirtualStringTree* tree, PVirtualNode parent, const TExternalDataSourceTable& tableData, int imgIndex) { PVirtualNode tableNode = addChildNode(tree, parent, tableData.name, imgIndex); + addStringSection(tree, tableNode, L"Поля", TreeImage::Attributes, TreeImage::Attributes, tableData.fields); addStringSection(tree, tableNode, L"Формы", TreeImage::Forms, TreeImage::Forms, tableData.forms); addStringSection(tree, tableNode, L"Команды", TreeImage::Commands, TreeImage::Commands, tableData.commands); addStringSection(tree, tableNode, L"Макеты", TreeImage::Layouts, TreeImage::Layouts, tableData.layouts); + tree->Expanded[tableNode] = true; } - void fillExternalCubeTree(TVirtualStringTree* tree, PVirtualNode parent, - const TExternalDataSourceCube& cubeData, int imgIndex) + void fillExternalCubeTree(TVirtualStringTree* tree, PVirtualNode parent, const TExternalDataSourceCube& cubeData, int imgIndex) { PVirtualNode cubeNode = addChildNode(tree, parent, cubeData.name, imgIndex); @@ -275,8 +306,7 @@ namespace } } -void fillExternalDataSourceTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, - int imgIndex, TExternalDataSources* metadataObject) +void fillExternalDataSourceTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, int imgIndex, TExternalDataSources* metadataObject) { if (!metadataObject) return; @@ -399,9 +429,7 @@ String getSubsystemDisplayNameByGuid(v8catalog* cf, const MetadataVector& subsystems, - const String& parentGuid, int imgIndex) +void addSubsystemChildrenToTreeByGuid(TVirtualStringTree* tree, PVirtualNode parentNode, v8catalog* cf, const MetadataVector& subsystems, const String& parentGuid, int imgIndex) { if (!tree || !cf) return; @@ -438,17 +466,14 @@ void addSubsystemChildrenToTreeByGuid(TVirtualStringTree* tree, PVirtualNode par } } -void addSubsystemChildrenToTree(TVirtualStringTree* tree, PVirtualNode parentNode, - v8catalog* cf, const MetadataVector& subsystems, - TSubsystem* parentSubsystem, int imgIndex) +void addSubsystemChildrenToTree(TVirtualStringTree* tree, PVirtualNode parentNode, v8catalog* cf, const MetadataVector& subsystems, TSubsystem* parentSubsystem, int imgIndex) { if (!tree || !cf || !parentSubsystem) return; addSubsystemChildrenToTreeByGuid(tree, parentNode, cf, subsystems, parentSubsystem->guid, imgIndex); } -std::unordered_set collectChildSubsystemGuids(v8catalog* cf, - const MetadataVector& subsystems) +std::unordered_set collectChildSubsystemGuids(v8catalog* cf, const MetadataVector& subsystems) { std::unordered_set childSubsystemGuids; if (!cf) diff --git a/src/MetadataTreeBuilder.h b/src/MetadataTreeBuilder.h index 64496eb..53d0c3d 100644 --- a/src/MetadataTreeBuilder.h +++ b/src/MetadataTreeBuilder.h @@ -46,59 +46,44 @@ void initNode(VirtualTreeData* data, const String& name, int imageIndex, int age PVirtualNode addChildNode(TVirtualStringTree* tree, PVirtualNode parent, const String& name, int imageIndex, int age = DefaultTreeNodeAge); template -void addSection(TVirtualStringTree* tree, PVirtualNode parent, - const String& sectionName, int sectionImageIndex, int itemImageIndex, - const Collection& items, NameGetter getName, int age = DefaultTreeNodeAge) +void addSection(TVirtualStringTree* tree, PVirtualNode parent, const String& sectionName, int sectionImageIndex, int itemImageIndex, const Collection& items, NameGetter getName, int age = DefaultTreeNodeAge) { PVirtualNode sectionNode = addChildNode(tree, parent, sectionName, sectionImageIndex, age); for (const auto& item : items) addChildNode(tree, sectionNode, getName(item), itemImageIndex, age); } -void addTabularSections(TVirtualStringTree* tree, PVirtualNode parent, - const std::vector>& items, int age = DefaultTreeNodeAge); +void addTabularSections(TVirtualStringTree* tree, PVirtualNode parent, const std::vector>& items, int age = DefaultTreeNodeAge); //--------------------------------------------------------------------------- // Секции для стандартных объектов и регистров -void fillStandardMetadataSections(TVirtualStringTree* tree, PVirtualNode childNode, - BaseMetadataObject* metadataObject); +void fillStandardMetadataSections(TVirtualStringTree* tree, PVirtualNode childNode, BaseMetadataObject* metadataObject); -void fillInformationRegisterSections(TVirtualStringTree* tree, PVirtualNode childNode, - MetadataObjectInformationRegister* metadataObject); +void fillInformationRegisterSections(TVirtualStringTree* tree, PVirtualNode childNode, MetadataObjectInformationRegister* metadataObject); //--------------------------------------------------------------------------- // fill*Tree — заполнение узла конкретного типа метаданных -void fillCatalogsTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, - int imgIndex, BaseMetadataObject* metadataObject); +void fillCatalogsTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, int imgIndex, BaseMetadataObject* metadataObject); -void fillFormsCommandsTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, - int imgIndex, BaseMetadataObject* metadataObject); +void fillFormsCommandsTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, int imgIndex, BaseMetadataObject* metadataObject); -void fillAccumulationRegisterTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, - int imgIndex, MetadataObjectInformationRegister* metadataObject); +void fillAccumulationRegisterTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, int imgIndex, MetadataObjectInformationRegister* metadataObject); -void fillCalculationRegisterTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, - int imgIndex, MetadataObjectInformationRegister* metadataObject); +void fillCalculationRegisterTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, int imgIndex, MetadataObjectInformationRegister* metadataObject); -void fillInformationRegisterTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, - int imgIndex, MetadataObjectInformationRegister* metadataObject); +void fillInformationRegisterTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, int imgIndex, MetadataObjectInformationRegister* metadataObject); -void fillAccountingRegisterTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, - int imgIndex, TAccountingRegisters* metadataObject); +void fillAccountingRegisterTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, int imgIndex, TAccountingRegisters* metadataObject); -void fillChartAccTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, - int imgIndex, TChartOfAccounts* metadataObject); +void fillChartAccTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, int imgIndex, TChartOfAccounts* metadataObject); -void fillJournalTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, - int imgIndex, BaseMetadataObject* metadataObject); +void fillJournalTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, int imgIndex, BaseMetadataObject* metadataObject); -void fillEnumTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, - int imgIndex, TEnums* curEnum); +void fillEnumTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, int imgIndex, TEnums* curEnum); -void fillExternalDataSourceTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, - int imgIndex, TExternalDataSources* metadataObject); +void fillExternalDataSourceTree(TVirtualStringTree* tree, PVirtualNode childNode, VirtualTreeData* childData, int imgIndex, TExternalDataSources* metadataObject); //--------------------------------------------------------------------------- // Вспомогательные функции для подсистем @@ -113,16 +98,11 @@ TSubsystem* findSubsystemByAnyGuid(v8catalog* cf, const MetadataVector& String getSubsystemDisplayNameByGuid(v8catalog* cf, const MetadataVector& subsystems, const String& guid); -void addSubsystemChildrenToTree(TVirtualStringTree* tree, PVirtualNode parentNode, - v8catalog* cf, const MetadataVector& subsystems, - TSubsystem* parentSubsystem, int imgIndex); +void addSubsystemChildrenToTree(TVirtualStringTree* tree, PVirtualNode parentNode, v8catalog* cf, const MetadataVector& subsystems, TSubsystem* parentSubsystem, int imgIndex); -void addSubsystemChildrenToTreeByGuid(TVirtualStringTree* tree, PVirtualNode parentNode, - v8catalog* cf, const MetadataVector& subsystems, - const String& parentGuid, int imgIndex); +void addSubsystemChildrenToTreeByGuid(TVirtualStringTree* tree, PVirtualNode parentNode, v8catalog* cf, const MetadataVector& subsystems, const String& parentGuid, int imgIndex); -std::unordered_set collectChildSubsystemGuids(v8catalog* cf, - const MetadataVector& subsystems); +std::unordered_set collectChildSubsystemGuids(v8catalog* cf, const MetadataVector& subsystems); //--------------------------------------------------------------------------- #endif diff --git a/src/ModuleTextStorage.cpp b/src/ModuleTextStorage.cpp new file mode 100644 index 0000000..724646d --- /dev/null +++ b/src/ModuleTextStorage.cpp @@ -0,0 +1,699 @@ +//--------------------------------------------------------------------------- + +#pragma hdrstop + +#include + +#include +#include + +#include "ModuleTextStorage.h" +#include "Class_1CD.h" + +//--------------------------------------------------------------------------- +#pragma package(smart_init) + +namespace +{ + bool __fastcall LooksLikeUtf16Le(const TBytes& bytes, int sourceSize) + { + int limit = sourceSize < 200 ? sourceSize : 200; + int checked = 0; + int zeroOdd = 0; + + for (int i = 1; i < limit; i += 2) + { + ++checked; + if (bytes[i] == 0) + ++zeroOdd; + } + + return checked > 0 && zeroOdd * 2 >= checked; + } + + String __fastcall DecodeModuleText(const TBytes& sourceBytes, int sourceSize, ModuleTextEncodingKind& encoding) + { + encoding = ModuleTextEncodingKind::Unknown; + + if (sourceSize <= 0 || sourceBytes.empty()) + return L""; + + TBytes bytes = sourceBytes; + sourceSize = sourceSize < bytes.Length ? sourceSize : bytes.Length; + + TEncoding* enc = nullptr; + int off = TEncoding::GetBufferEncoding(bytes, enc); + if (off > 0) + { + if (enc == TEncoding::UTF8) + encoding = ModuleTextEncodingKind::Utf8Bom; + else if (enc == TEncoding::Unicode) + encoding = ModuleTextEncodingKind::Utf16LeBom; + + TBytes unicodeBytes = TEncoding::Convert(enc, TEncoding::Unicode, bytes, off, sourceSize - off); + if (!unicodeBytes.empty()) + return String((wchar_t*)&unicodeBytes[0], unicodeBytes.Length / 2); + } + + if (LooksLikeUtf16Le(bytes, sourceSize)) + { + encoding = ModuleTextEncodingKind::Utf16Le; + return String((wchar_t*)&bytes[0], sourceSize / 2); + } + + try + { + encoding = ModuleTextEncodingKind::Utf8; + return TEncoding::UTF8->GetString(bytes, 0, sourceSize); + } + catch (...) + { + encoding = ModuleTextEncodingKind::Ansi; + return String((char*)&bytes[0], sourceSize); + } + } + + void __fastcall AddUniquePath(std::vector& values, const String& value) + { + if (value.IsEmpty()) + return; + + String normalized = ExcludeTrailingPathDelimiter(value).UpperCase(); + for (const auto& existing : values) + if (ExcludeTrailingPathDelimiter(existing).UpperCase() == normalized) + return; + + values.push_back(value); + } + + std::vector __fastcall GetSourceCfRoots() + { + std::vector roots; + AddUniquePath(roots, TPath::Combine(GetCurrentDir(), L"SourceCF")); + AddUniquePath(roots, TPath::Combine(ExtractFilePath(ParamStr(0)), L"SourceCF")); + return roots; + } + + void __fastcall AddUniqueString(std::vector& values, const String& value) + { + if (value.IsEmpty()) + return; + + String upperValue = value.UpperCase(); + for (const auto& existing : values) + if (existing.UpperCase() == upperValue) + return; + + values.push_back(value); + } + + std::vector __fastcall GetModuleContainerCandidates(const String& baseGuid, + ModuleTextKind kind = ModuleTextKind::Unknown) + { + std::vector candidates; + + if (kind == ModuleTextKind::ManagerModule) + { + AddUniqueString(candidates, baseGuid + L".3"); + AddUniqueString(candidates, baseGuid + L".1"); + AddUniqueString(candidates, baseGuid + L".2"); + AddUniqueString(candidates, baseGuid + L".4"); + AddUniqueString(candidates, baseGuid + L".5"); + AddUniqueString(candidates, baseGuid + L".0"); + } + else + { + AddUniqueString(candidates, baseGuid + L".0"); + AddUniqueString(candidates, baseGuid + L".1"); + AddUniqueString(candidates, baseGuid + L".2"); + AddUniqueString(candidates, baseGuid + L".3"); + AddUniqueString(candidates, baseGuid + L".4"); + AddUniqueString(candidates, baseGuid + L".5"); + } + + AddUniqueString(candidates, baseGuid); + return candidates; + } + + String __fastcall ReadDiskFileRawText(const String& filePath, ModuleTextEncodingKind& encoding) + { + if (!FileExists(filePath)) + { + encoding = ModuleTextEncodingKind::Unknown; + return L""; + } + + std::unique_ptr fs(new TFileStream(filePath, fmOpenRead | fmShareDenyNone)); + TBytes bytes; + std::unique_ptr sb(new TBytesStream(bytes)); + sb->CopyFrom(fs.get(), 0); + return DecodeModuleText(sb->Bytes, sb->Size, encoding); + } + + bool __fastcall TryReadDiskModuleFile(const String& filePath, + ModuleTextKind kind, + const String& metadataGuid, + const String& moduleDataGuid, + ModuleTextDocument& document) + { + ModuleTextEncodingKind encoding; + String text = ReadDiskFileRawText(filePath, encoding); + if (!FileExists(filePath)) + return false; + + if (!text.IsEmpty() && !ModuleTextStorage::LooksLike1CModuleText(text)) + return false; + + document.text = text; + document.loaded = true; + document.dirty = false; + document.location.metadataGuid = metadataGuid; + document.location.moduleDataGuid = moduleDataGuid; + document.location.filePath = filePath; + document.location.fileName = ExtractFileName(filePath); + document.location.containerPath = ExtractFileDir(filePath); + document.location.sourceRoot = ExtractFileDir(document.location.containerPath); + document.location.kind = kind; + document.location.encoding = encoding; + document.location.editable = true; + document.location.foundInSourceCF = true; + return true; + } + + bool __fastcall TryLoadFromSourceCfByGuid(const String& metadataGuid, + ModuleTextKind kind, + ModuleTextDocument& document) + { + const String normalizedGuid = ModuleTextStorage::NormalizeGuidFileName(metadataGuid); + if (normalizedGuid.IsEmpty()) + return false; + + ModuleTextDocument emptyFallback; + bool hasEmptyFallback = false; + + for (const auto& sourceRoot : GetSourceCfRoots()) + { + if (!TDirectory::Exists(sourceRoot)) + continue; + + for (const auto& containerName : GetModuleContainerCandidates(normalizedGuid, kind)) + { + ModuleTextDocument candidate; + const String containerPath = TPath::Combine(sourceRoot, containerName); + const String textPath = TPath::Combine(containerPath, L"text"); + if (TryReadDiskModuleFile(textPath, kind, metadataGuid, containerName, candidate)) + { + if (kind != ModuleTextKind::ManagerModule || !candidate.text.IsEmpty()) + { + document = candidate; + return true; + } + + if (!hasEmptyFallback) + { + emptyFallback = candidate; + hasEmptyFallback = true; + } + } + + const String modulePath = TPath::Combine(containerPath, L"module"); + if (TryReadDiskModuleFile(modulePath, kind, metadataGuid, containerName, candidate)) + { + if (kind != ModuleTextKind::ManagerModule || !candidate.text.IsEmpty()) + { + document = candidate; + return true; + } + + if (!hasEmptyFallback) + { + emptyFallback = candidate; + hasEmptyFallback = true; + } + } + + const String directPath = TPath::Combine(sourceRoot, containerName); + if (TryReadDiskModuleFile(directPath, kind, metadataGuid, containerName, candidate)) + { + if (kind != ModuleTextKind::ManagerModule || !candidate.text.IsEmpty()) + { + document = candidate; + return true; + } + + if (!hasEmptyFallback) + { + emptyFallback = candidate; + hasEmptyFallback = true; + } + } + } + } + + if (hasEmptyFallback) + { + document = emptyFallback; + return true; + } + + return false; + } + + bool __fastcall TryLoadFromSourceCfByModuleName(const String& moduleName, + ModuleTextKind kind, + ModuleTextDocument& document) + { + if (moduleName.IsEmpty()) + return false; + + for (const auto& sourceRoot : GetSourceCfRoots()) + { + if (!TDirectory::Exists(sourceRoot)) + continue; + + TStringDynArray files = TDirectory::GetFiles(sourceRoot); + for (int i = 0; i < files.Length; ++i) + { + const String metadataFile = files[i]; + const String objectGuid = ExtractFileName(metadataFile); + if (!ModuleTextStorage::IsGuidLike(objectGuid)) + continue; + + ModuleTextEncodingKind encoding; + String metadataText = ReadDiskFileRawText(metadataFile, encoding); + if (metadataText.Pos(moduleName) <= 0) + continue; + + if (TryLoadFromSourceCfByGuid(objectGuid, kind, document)) + return true; + } + } + + return false; + } + + void __fastcall CollectGuidReferences(tree* node, std::vector& guids) + { + if (!node) + return; + + if (ModuleTextStorage::IsGuidLike(node->get_value())) + AddUniqueString(guids, node->get_value()); + + for (int i = 0; i < node->get_num_subnode(); i++) + CollectGuidReferences(node->get_subnode(i), guids); + } + + String __fastcall FindEmbeddedModuleText(tree* node) + { + if (!node) + return L""; + + if (node->get_type() == nd_string && ModuleTextStorage::LooksLike1CModuleText(node->get_value())) + return node->get_value(); + + for (int i = 0; i < node->get_num_subnode(); i++) + { + String found = FindEmbeddedModuleText(node->get_subnode(i)); + if (!found.IsEmpty()) + return found; + } + + return L""; + } + + String __fastcall ReadV8FileAsText(v8file* file) + { + if (!file) + return L""; + + TBytes bytes; + TBytesStream* sb = new TBytesStream(bytes); + try + { + file->SaveToStream(sb); + ModuleTextEncodingKind encoding; + String text = DecodeModuleText(sb->Bytes, sb->Size, encoding); + delete sb; + return text; + } + catch (...) + { + delete sb; + return L""; + } + } + + String __fastcall TryReadNamedTextFile(v8catalog* catalog, const String& fileName) + { + if (!catalog) + return L""; + + v8file* file = catalog->GetFile(fileName); + if (!file) + return L""; + + String text = ReadV8FileAsText(file); + return ModuleTextStorage::LooksLike1CModuleText(text) ? text : L""; + } + + String __fastcall TryReadModuleContainer(v8file* file) + { + if (!file) + return L""; + + try + { + v8catalog* catalog = new v8catalog(file); + if (catalog) + { + catalog->ClearIs8316(); + + String text = TryReadNamedTextFile(catalog, L"text"); + if (text.IsEmpty()) + text = TryReadNamedTextFile(catalog, L"module"); + + if (text.IsEmpty()) + { + for (v8file* child = catalog->GetFirst(); child; child = child->GetNext()) + { + String childName = child->GetFileName().LowerCase(); + if (childName.Pos(L"text") > 0 || childName.Pos(L"module") > 0) + { + text = ReadV8FileAsText(child); + if (ModuleTextStorage::LooksLike1CModuleText(text)) + break; + text = L""; + } + } + } + + delete catalog; + if (!text.IsEmpty()) + return text; + } + } + catch (...) + { + } + + String directText = ReadV8FileAsText(file); + if (ModuleTextStorage::LooksLike1CModuleText(directText)) + return directText; + + try + { + std::unique_ptr objectTree(get_treeFromV8file(file)); + return FindEmbeddedModuleText(objectTree.get()); + } + catch (...) + { + return L""; + } + } + + void __fastcall WriteTextWithEncoding(TStream* stream, const String& text, ModuleTextEncodingKind encoding) + { + TBytes bytes; + + switch (encoding) + { + case ModuleTextEncodingKind::Utf16LeBom: + { + const unsigned char bom[] = {0xFF, 0xFE}; + stream->WriteBuffer((void*)bom, 2); + bytes = TEncoding::Unicode->GetBytes(text); + break; + } + case ModuleTextEncodingKind::Utf16Le: + { + bytes = TEncoding::Unicode->GetBytes(text); + break; + } + case ModuleTextEncodingKind::Utf8Bom: + { + const unsigned char bom[] = {0xEF, 0xBB, 0xBF}; + stream->WriteBuffer((void*)bom, 3); + bytes = TEncoding::UTF8->GetBytes(text); + break; + } + case ModuleTextEncodingKind::Ansi: + { + bytes = TEncoding::Default->GetBytes(text); + break; + } + case ModuleTextEncodingKind::Utf8: + case ModuleTextEncodingKind::Unknown: + default: + { + bytes = TEncoding::UTF8->GetBytes(text); + break; + } + } + + if (!bytes.empty()) + stream->WriteBuffer(&bytes[0], bytes.Length); + } +} + +namespace ModuleTextStorage +{ + bool __fastcall LooksLike1CModuleText(const String& value) + { + return value.Length() > 0 + && (value.Pos(L"\n") > 0 + || value.Pos(L"\r") > 0 + || value.Pos(L"Процедура") > 0 + || value.Pos(L"Функция") > 0 + || value.Pos(L"КонецПроцедуры") > 0 + || value.Pos(L"КонецФункции") > 0); + } + + bool __fastcall IsGuidLike(const String& value) + { + if (value.Length() != 36) + return false; + + for (int i = 1; i <= value.Length(); i++) + { + wchar_t ch = value[i]; + bool dash = i == 9 || i == 14 || i == 19 || i == 24; + if (dash) + { + if (ch != L'-') + return false; + } + else + { + bool hex = (ch >= L'0' && ch <= L'9') + || (ch >= L'a' && ch <= L'f') + || (ch >= L'A' && ch <= L'F'); + if (!hex) + return false; + } + } + + return true; + } + + String __fastcall NormalizeGuidFileName(const String& guid) + { + String result = Trim(guid).LowerCase(); + if (result.Length() >= 2 && result[1] == L'{' && result[result.Length()] == L'}') + result = result.SubString(2, result.Length() - 2); + return result; + } + + ModuleTextDocument __fastcall LoadCommonModule(v8catalog* parent, const String& metadataGuid, const String& moduleName) + { + ModuleTextDocument document; + document.loaded = true; + document.location.kind = ModuleTextKind::CommonModule; + document.location.metadataGuid = metadataGuid; + + if (TryLoadFromSourceCfByGuid(metadataGuid, ModuleTextKind::CommonModule, document)) + return document; + + if (TryLoadFromSourceCfByModuleName(moduleName, ModuleTextKind::CommonModule, document)) + return document; + + if (parent && !metadataGuid.IsEmpty()) + { + std::vector candidates = GetModuleContainerCandidates(NormalizeGuidFileName(metadataGuid), ModuleTextKind::CommonModule); + + try + { + v8file* objectFile = parent->GetFile(metadataGuid); + if (objectFile) + { + std::unique_ptr objectTree(get_treeFromV8file(objectFile)); + document.text = FindEmbeddedModuleText(objectTree.get()); + + std::vector referencedGuids; + CollectGuidReferences(objectTree.get(), referencedGuids); + for (const auto& referencedGuid : referencedGuids) + { + if (TryLoadFromSourceCfByGuid(referencedGuid, ModuleTextKind::CommonModule, document)) + return document; + for (const auto& candidate : GetModuleContainerCandidates(NormalizeGuidFileName(referencedGuid), ModuleTextKind::CommonModule)) + AddUniqueString(candidates, candidate); + } + } + } + catch (...) + { + } + + for (size_t i = 0; i < candidates.size(); i++) + { + v8file* dataModule = parent->GetFile(candidates[i]); + String text = TryReadModuleContainer(dataModule); + if (!text.IsEmpty()) + { + document.text = text; + document.location.moduleDataGuid = candidates[i]; + document.location.editable = false; + document.location.foundInSourceCF = false; + return document; + } + } + } + + return document; + } + + ModuleTextDocument __fastcall LoadCommonForm(v8catalog* parent, const String& metadataGuid, const String& formName) + { + ModuleTextDocument document; + document.loaded = true; + document.location.kind = ModuleTextKind::CommonForm; + document.location.metadataGuid = metadataGuid; + + if (TryLoadFromSourceCfByGuid(metadataGuid, ModuleTextKind::CommonForm, document)) + return document; + + if (parent && !metadataGuid.IsEmpty()) + { + v8file* dataForm = parent->GetFile(metadataGuid + L".0"); + String text = TryReadModuleContainer(dataForm); + if (!text.IsEmpty()) + { + document.text = text; + document.location.moduleDataGuid = metadataGuid + L".0"; + document.location.editable = false; + document.location.foundInSourceCF = false; + } + } + + return document; + } + + ModuleTextDocument __fastcall LoadByMetadataObject(v8catalog* parent, const String& metadataGuid, const String& objectName, ModuleTextKind kind) + { + ModuleTextDocument document; + document.loaded = true; + document.location.kind = kind; + document.location.metadataGuid = metadataGuid; + + if (TryLoadFromSourceCfByGuid(metadataGuid, kind, document)) + return document; + + if (parent && !metadataGuid.IsEmpty()) + { + std::vector candidates = GetModuleContainerCandidates(NormalizeGuidFileName(metadataGuid), kind); + + try + { + v8file* objectFile = parent->GetFile(metadataGuid); + if (objectFile) + { + std::unique_ptr objectTree(get_treeFromV8file(objectFile)); + + std::vector referencedGuids; + CollectGuidReferences(objectTree.get(), referencedGuids); + for (const auto& referencedGuid : referencedGuids) + { + if (TryLoadFromSourceCfByGuid(referencedGuid, kind, document)) + return document; + for (const auto& candidate : GetModuleContainerCandidates(NormalizeGuidFileName(referencedGuid), kind)) + AddUniqueString(candidates, candidate); + } + } + } + catch (...) + { + } + + for (size_t i = 0; i < candidates.size(); i++) + { + v8file* file = parent->GetFile(candidates[i]); + String text = TryReadModuleContainer(file); + if (!text.IsEmpty()) + { + document.text = text; + document.location.moduleDataGuid = candidates[i]; + document.location.editable = false; + document.location.foundInSourceCF = false; + return document; + } + } + } + + return document; + } + + bool __fastcall SaveDocument(ModuleTextDocument& document, const String& newText, String& errorText) + { + errorText = L""; + + if (!document.location.editable || document.location.filePath.IsEmpty()) + { + errorText = L"Не найден редактируемый файл модуля в SourceCF."; + return false; + } + + const String target = document.location.filePath; + const String dir = ExtractFileDir(target); + if (!TDirectory::Exists(dir)) + { + errorText = L"Каталог модуля не существует: " + dir; + return false; + } + + const String tmp = TPath::Combine(dir, ExtractFileName(target) + L".codex.tmp"); + try + { + { + std::unique_ptr out(new TFileStream(tmp, fmCreate)); + WriteTextWithEncoding(out.get(), newText, document.location.encoding); + } + + if (FileExists(target)) + TFile::Delete(target); + TFile::Move(tmp, target); + + document.text = newText; + document.dirty = false; + document.loaded = true; + return true; + } + catch (const Exception& e) + { + if (FileExists(tmp)) + TFile::Delete(tmp); + errorText = e.Message; + return false; + } + } + + String __fastcall DescribeLocation(const ModuleTextLocation& location) + { + if (!location.filePath.IsEmpty()) + return location.filePath; + if (!location.containerPath.IsEmpty()) + return location.containerPath; + if (!location.moduleDataGuid.IsEmpty()) + return location.moduleDataGuid; + return L""; + } +} diff --git a/src/ModuleTextStorage.h b/src/ModuleTextStorage.h new file mode 100644 index 0000000..c65f522 --- /dev/null +++ b/src/ModuleTextStorage.h @@ -0,0 +1,69 @@ +//--------------------------------------------------------------------------- + +#ifndef ModuleTextStorageH +#define ModuleTextStorageH + +#include +#include + +#include "APIcfBase.h" +#include "Parse_tree.h" + +enum class ModuleTextKind +{ + Unknown, + CommonModule, + CommonForm, + ObjectModule, + ManagerModule, + FormModule, + CommandModule +}; + +enum class ModuleTextEncodingKind +{ + Unknown, + Utf8, + Utf8Bom, + Utf16Le, + Utf16LeBom, + Ansi +}; + +struct ModuleTextLocation +{ + String metadataGuid; + String moduleDataGuid; + String sourceRoot; + String containerPath; + String filePath; + String fileName; + ModuleTextKind kind = ModuleTextKind::Unknown; + ModuleTextEncodingKind encoding = ModuleTextEncodingKind::Unknown; + bool editable = false; + bool foundInSourceCF = false; +}; + +struct ModuleTextDocument +{ + String text; + ModuleTextLocation location; + bool loaded = false; + bool dirty = false; +}; + +namespace ModuleTextStorage +{ + String __fastcall NormalizeGuidFileName(const String& guid); + bool __fastcall IsGuidLike(const String& value); + bool __fastcall LooksLike1CModuleText(const String& value); + + ModuleTextDocument __fastcall LoadCommonModule(v8catalog* parent, const String& metadataGuid, const String& moduleName); + ModuleTextDocument __fastcall LoadCommonForm(v8catalog* parent, const String& metadataGuid, const String& formName); + ModuleTextDocument __fastcall LoadByMetadataObject(v8catalog* parent, const String& metadataGuid, const String& objectName, ModuleTextKind kind); + + bool __fastcall SaveDocument(ModuleTextDocument& document, const String& newText, String& errorText); + String __fastcall DescribeLocation(const ModuleTextLocation& location); +} + +#endif