You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
285 lines
7.6 KiB
285 lines
7.6 KiB
// 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 <neilh@scintilla.org> |
|
// The License.txt file describes the conditions under which this software may be distributed. |
|
|
|
#include <cstring> |
|
|
|
#include <string> |
|
#include <string_view> |
|
#include <vector> |
|
#include <set> |
|
|
|
#if !defined(_WIN32) |
|
#include <dlfcn.h> |
|
#else |
|
#include <windows.h> |
|
#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<typename T> |
|
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<int>(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<LexLibrary> libraries; |
|
|
|
std::vector<std::string> lexers; |
|
std::vector<std::string> 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<GetLexerCountFn>( |
|
FindSymbol(lexillaDL, LEXILLA_GETLEXERCOUNT)); |
|
GetLexerNameFn fnLexerName = FunctionPointer<GetLexerNameFn>( |
|
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<CreateLexerFn>( |
|
FindSymbol(lexillaDL, LEXILLA_CREATELEXER)); |
|
LexerNameFromIDFn fnLNFI = FunctionPointer<LexerNameFromIDFn>( |
|
FindSymbol(lexillaDL, LEXILLA_LEXERNAMEFROMID)); |
|
GetLibraryPropertyNamesFn fnGLPN = FunctionPointer<GetLibraryPropertyNamesFn>( |
|
FindSymbol(lexillaDL, LEXILLA_GETLIBRARYPROPERTYNAMES)); |
|
SetLibraryPropertyFn fnSLP = FunctionPointer<SetLibraryPropertyFn>( |
|
FindSymbol(lexillaDL, LEXILLA_SETLIBRARYPROPERTY)); |
|
GetNameSpaceFn fnGNS = FunctionPointer<GetNameSpaceFn>( |
|
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<std::string> 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<std::string>(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<std::string> 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<std::string> 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. |
|
}
|
|
|