// SciTE - Scintilla based Text Editor /** @file LexillaAccess.cxx ** Interface to loadable lexers. ** Maintains a list of lexer library paths and CreateLexer functions. ** If list changes then load all the lexer libraries and find the functions. ** When asked to create a lexer, call each function until one succeeds. **/ // Copyright 2019 by Neil Hodgson // The License.txt file describes the conditions under which this software may be distributed. #include #include #include #include #include #if !defined(_WIN32) #include #else #include #endif #include "ILexer.h" #include "Lexilla.h" #include "LexillaAccess.h" namespace { #if defined(_WIN32) typedef FARPROC Function; typedef HMODULE Module; constexpr const char *pathSeparator = "\\"; #else typedef void *Function; typedef void *Module; constexpr const char *pathSeparator = "/"; #endif /// Generic function to convert from a Function(void* or FARPROC) to a function pointer. /// This avoids undefined and conditionally defined behaviour. template T FunctionPointer(Function function) noexcept { static_assert(sizeof(T) == sizeof(function)); T fp {}; memcpy(&fp, &function, sizeof(T)); return fp; } #if defined(_WIN32) std::wstring WideStringFromUTF8(std::string_view sv) { const int sLength = static_cast(sv.length()); const int cchWide = ::MultiByteToWideChar(CP_UTF8, 0, sv.data(), sLength, nullptr, 0); std::wstring sWide(cchWide, 0); ::MultiByteToWideChar(CP_UTF8, 0, sv.data(), sLength, sWide.data(), cchWide); return sWide; } #endif // Turn off deprecation checks as LexillaAccess deprecates its wrapper over // the deprecated LexerNameFromID. Thus use within LexillaAccess is intentional. #if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #else #pragma warning(disable: 4996) #endif std::string directoryLoadDefault; std::string lastLoaded; struct LexLibrary { Lexilla::CreateLexerFn fnCL; Lexilla::LexerNameFromIDFn fnLNFI; Lexilla::GetLibraryPropertyNamesFn fnGLPN; Lexilla::SetLibraryPropertyFn fnSLP; std::string nameSpace; }; std::vector libraries; std::vector lexers; std::vector libraryProperties; Function FindSymbol(Module m, const char *symbol) noexcept { #if defined(_WIN32) return ::GetProcAddress(m, symbol); #else return dlsym(m, symbol); #endif } Lexilla::CreateLexerFn pCreateLexerDefault = nullptr; bool NameContainsDot(std::string_view path) noexcept { for (std::string_view::const_reverse_iterator it = path.crbegin(); it != path.crend(); ++it) { if (*it == '.') return true; if (*it == '/' || *it == '\\') return false; } return false; } constexpr bool HasPrefix(std::string_view s, std::string_view prefix) noexcept { return (s.size() >= prefix.size()) && (prefix == s.substr(0, prefix.size())); } } void Lexilla::SetDefault(CreateLexerFn pCreate) noexcept { pCreateLexerDefault = pCreate; } void Lexilla::SetDefaultDirectory(std::string_view directory) { directoryLoadDefault = directory; } bool Lexilla::Load(std::string_view sharedLibraryPaths) { if (sharedLibraryPaths == lastLoaded) { return !libraries.empty(); } std::string_view paths = sharedLibraryPaths; lexers.clear(); libraries.clear(); while (!paths.empty()) { const size_t separator = paths.find_first_of(';'); std::string path(paths.substr(0, separator)); if (separator == std::string::npos) { paths.remove_prefix(paths.size()); } else { paths.remove_prefix(separator + 1); } if (path == ".") { if (directoryLoadDefault.empty()) { path = ""; } else { path = directoryLoadDefault; path += pathSeparator; } path += LEXILLA_LIB; } if (!NameContainsDot(path)) { // No '.' in name so add extension path.append(LEXILLA_EXTENSION); } #if defined(_WIN32) // Convert from UTF-8 to wide characters std::wstring wsPath = WideStringFromUTF8(path); Module lexillaDL = ::LoadLibraryW(wsPath.c_str()); #else Module lexillaDL = dlopen(path.c_str(), RTLD_LAZY); #endif if (lexillaDL) { GetLexerCountFn fnLexerCount = FunctionPointer( FindSymbol(lexillaDL, LEXILLA_GETLEXERCOUNT)); GetLexerNameFn fnLexerName = FunctionPointer( FindSymbol(lexillaDL, LEXILLA_GETLEXERNAME)); if (fnLexerCount && fnLexerName) { const int nLexers = fnLexerCount(); for (int i = 0; i < nLexers; i++) { char name[100] = ""; fnLexerName(i, name, sizeof(name)); lexers.push_back(name); } } CreateLexerFn fnCL = FunctionPointer( FindSymbol(lexillaDL, LEXILLA_CREATELEXER)); LexerNameFromIDFn fnLNFI = FunctionPointer( FindSymbol(lexillaDL, LEXILLA_LEXERNAMEFROMID)); GetLibraryPropertyNamesFn fnGLPN = FunctionPointer( FindSymbol(lexillaDL, LEXILLA_GETLIBRARYPROPERTYNAMES)); SetLibraryPropertyFn fnSLP = FunctionPointer( FindSymbol(lexillaDL, LEXILLA_SETLIBRARYPROPERTY)); GetNameSpaceFn fnGNS = FunctionPointer( FindSymbol(lexillaDL, LEXILLA_GETNAMESPACE)); std::string nameSpace; if (fnGNS) { nameSpace = fnGNS(); nameSpace += LEXILLA_NAMESPACE_SEPARATOR; } LexLibrary lexLib { fnCL, fnLNFI, fnGLPN, fnSLP, nameSpace }; libraries.push_back(lexLib); } } lastLoaded = sharedLibraryPaths; std::set nameSet; for (const LexLibrary &lexLib : libraries) { if (lexLib.fnGLPN) { const char *cpNames = lexLib.fnGLPN(); if (cpNames) { std::string_view names = cpNames; while (!names.empty()) { const size_t separator = names.find_first_of('\n'); std::string name(names.substr(0, separator)); nameSet.insert(name); if (separator == std::string::npos) { names.remove_prefix(names.size()); } else { names.remove_prefix(separator + 1); } } } } } // Standard Lexilla does not have any properties so can't be added to set. libraryProperties = std::vector(nameSet.begin(), nameSet.end()); return !libraries.empty(); } Scintilla::ILexer5 *Lexilla::MakeLexer(std::string_view languageName) { std::string sLanguageName(languageName); // Ensure NUL-termination // First, try to match namespace then name suffix for (const LexLibrary &lexLib : libraries) { if (lexLib.fnCL && !lexLib.nameSpace.empty()) { if (HasPrefix(languageName, lexLib.nameSpace)) { Scintilla::ILexer5 *pLexer = lexLib.fnCL(sLanguageName.substr(lexLib.nameSpace.size()).c_str()); if (pLexer) { return pLexer; } } } } // If no match with namespace, try to just match name for (const LexLibrary &lexLib : libraries) { if (lexLib.fnCL) { Scintilla::ILexer5 *pLexer = lexLib.fnCL(sLanguageName.c_str()); if (pLexer) { return pLexer; } } } if (pCreateLexerDefault) { return pCreateLexerDefault(sLanguageName.c_str()); } #if defined(LEXILLA_STATIC) Scintilla::ILexer5 *pLexer = CreateLexer(sLanguageName.c_str()); if (pLexer) { return pLexer; } #endif return nullptr; } std::vector Lexilla::Lexers() { return lexers; } std::string Lexilla::NameFromID(int identifier) { for (const LexLibrary &lexLib : libraries) { if (lexLib.fnLNFI) { const char *name = lexLib.fnLNFI(identifier); if (name) { return name; } } } return std::string(); } std::vector Lexilla::LibraryProperties() { return libraryProperties; } void Lexilla::SetProperty(const char *key, const char *value) { for (const LexLibrary &lexLib : libraries) { if (lexLib.fnSLP) { lexLib.fnSLP(key, value); } } // Standard Lexilla does not have any properties so don't set. }