// SciTE - Scintilla based Text Editor
/** @file SciTEProps.cxx
 ** Properties management.
 **/
// Copyright 1998-2011 by Neil Hodgson <neilh@scintilla.org>
// The License.txt file describes the conditions under which this software may be distributed.

#include <cstddef>
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <cstdio>
#include <ctime>
#include <clocale>

#include <stdexcept>
#include <utility>
#include <compare>
#include <tuple>
#include <string>
#include <string_view>
#include <vector>
#include <map>
#include <set>
#include <optional>
#include <algorithm>
#include <ranges>
#include <memory>
#include <chrono>
#include <atomic>
#include <mutex>

#include <fcntl.h>

#include "ScintillaTypes.h"
#include "ScintillaMessages.h"
#include "ScintillaCall.h"
#include "ILexer.h"
#include "Scintilla.h"
#include "SciLexer.h"
#include "Lexilla.h"
#include "LexillaAccess.h"

#include "GUI.h"
#include "ScintillaWindow.h"

#if (defined(__unix__) || defined(__APPLE__)) && !defined(QT_QML)

const GUI::gui_char menuAccessIndicator[] = GUI_TEXT("_");

#else

const GUI::gui_char menuAccessIndicator[] = GUI_TEXT("&");

#endif

#include "StringList.h"
#include "StringHelpers.h"
#include "FilePath.h"
#include "StyleDefinition.h"
#include "PropSetFile.h"
#include "StyleWriter.h"
#include "Extender.h"
#include "SciTE.h"
#include "JobQueue.h"
#include "Cookie.h"
#include "Worker.h"
#include "MatchMarker.h"
#include "EditorConfig.h"
#include "Searcher.h"
#include "SciTEBase.h"
#include "IFaceTable.h"

void SciTEBase::SetImportMenu() {
	for (int i = 0; i < importMax; i++) {
		DestroyMenuItem(menuOptions, importCmdID + i);
	}
	if (!importFiles.empty()) {
		for (int stackPos = 0; stackPos < static_cast<int>(importFiles.size()) && stackPos < importMax; stackPos++) {
			const int itemID = importCmdID + stackPos;
			if (importFiles[stackPos].IsSet()) {
				GUI::gui_string entry = localiser.Text("Open");
				entry += GUI_TEXT(" ");
				entry += importFiles[stackPos].Name().AsText();
				SetMenuItem(menuOptions, IMPORT_START + stackPos, itemID, entry.c_str());
			}
		}
	}
}

void SciTEBase::ImportMenu(int pos) {
	if (pos >= 0) {
		if (importFiles[pos].IsSet()) {
			Open(importFiles[pos]);
		}
	}
}

void SciTEBase::SetLanguageMenu() {
	for (int i = 0; i < 100; i++) {
		DestroyMenuItem(menuLanguage, languageCmdID + i);
	}
	for (unsigned int item = 0; item < languageMenu.size(); item++) {
		const int itemID = languageCmdID + item;
		const GUI::gui_string entry = localiser.Text(languageMenu[item].menuItem);
		const GUI::gui_string menuKey = GUI::StringFromUTF8(languageMenu[item].menuKey);
		if (!entry.empty() && entry[0] != '#') {
			SetMenuItem(menuLanguage, item, itemID, entry.c_str(), menuKey.c_str());
		}
	}
}

// Null except on Windows where it may be overridden
void SciTEBase::ReadEmbeddedProperties() {
}

const GUI::gui_char propLocalFileName[] = GUI_TEXT("SciTE.properties");
const GUI::gui_char propDirectoryFileName[] = GUI_TEXT("SciTEDirectory.properties");

void SciTEBase::ReadEnvironment() {
#if defined(__unix__) || defined(__APPLE__)
	extern char **environ;
	char **e = environ;
#else
	char **e = _environ;
#endif
	for (; e && *e; e++) {
		const auto [key, value] = ViewSplit(*e, '=');
		propsPlatform.Set(key, value);
	}

	propsPlatform.SetPath("SciteDefaultHome", GetSciteDefaultHome());
	propsPlatform.SetPath("SciteUserHome", GetSciteUserHome());
}

/**
Read global and user properties files.
*/
void SciTEBase::ReadGlobalPropFile() {
	// Appearance and Contrast may be read in embedded or global properties
	// so set them in deepest property set propsPlatform.
	propsPlatform.Set("Appearance", StdStringFromInteger(appearance.dark));
	propsPlatform.Set("Contrast", StdStringFromInteger(appearance.highContrast));

	std::string excludes;
	std::string includes;

	// Want to apply imports.exclude and imports.include but these may well be in
	// user properties.

	for (int attempt=0; attempt<2; attempt++) {

		std::string excludesRead = props.GetString("imports.exclude");
		std::string includesRead = props.GetString("imports.include");
		if ((attempt > 0) && ((excludesRead == excludes) && (includesRead == includes)))
			break;

		excludes = excludesRead;
		includes = includesRead;

		filter.SetFilter(excludes, includes);

		importFiles.clear();

		ReadEmbeddedProperties();

		propsBase.Clear();
		FilePath propfileBase = GetDefaultPropertiesFileName();
		propsBase.Read(propfileBase, propfileBase.Directory(), filter, &importFiles, 0);

		propsUser.Clear();
		FilePath propfileUser = GetUserPropertiesFileName();
		propsUser.Read(propfileUser, propfileUser.Directory(), filter, &importFiles, 0);
	}

	if (!localiser.read) {
		ReadLocalization();
	}
}

void SciTEBase::ReadAbbrevPropFile() {
	propsAbbrev.Clear();
	propsAbbrev.Read(pathAbbreviations, pathAbbreviations.Directory(), filter, &importFiles, 0);
}

/**
Reads the directory properties file depending on the variable
"properties.directory.enable". Also sets the variable $(SciteDirectoryHome) to the path
where this property file is found. If it is not found $(SciteDirectoryHome) will
be set to $(FilePath).
*/
void SciTEBase::ReadDirectoryPropFile() {
	propsDirectory.Clear();

	propsDirectory.SetPath("FilePath", filePath);
	propsDirectory.SetPath("FileDir", filePath.Directory());
	propsDirectory.SetPath("FileName", filePath.BaseName());
	propsDirectory.SetPath("FileExt", filePath.Extension());
	propsDirectory.SetPath("FileNameExt", FileNameExt());

	if (propsUser.GetInt("properties.directory.enable") != 0) {
		const FilePath propfile = GetDirectoryPropertiesFileName();
		const FilePath propfileDirectory = propfile.Directory();
		const GUI::gui_string relPath = propfileDirectory.RelativePathTo(filePath);
		propsDirectory.Set("RelativePath", GUI::UTF8FromString(relPath));

		props.SetPath("SciteDirectoryHome", propfileDirectory);

		propsDirectory.Read(propfile, propfileDirectory, filter, nullptr, 0);
	} else {
		propsDirectory.SetPath("RelativePath", filePath.Name());
	}
	propsDirectory.Unset("SciteDefaultHome");
	propsDirectory.Unset("SciteUserHome");
}

/**
Read local and directory properties file.
*/
void SciTEBase::ReadLocalPropFile() {
	// The directory properties acts like a base local properties file.
	// Therefore it must be read always before reading the local prop file.
	ReadDirectoryPropFile();

	FilePath propfile = GetLocalPropertiesFileName();

	propsLocal.Clear();
	if (propsUser.GetInt("properties.local.enable", 1) != 0) {
		propsLocal.Read(propfile, propfile.Directory(), filter, nullptr, 0);
		propsLocal.Unset("SciteDefaultHome");
		propsLocal.Unset("SciteUserHome");
	}

	props.Set("Chrome", "#C0C0C0");
	props.Set("ChromeHighlight", "#FFFFFF");

	FilePath fileDirectory = filePath.Directory();
	editorConfig->Clear();
	if (props.GetInt("editor.config.enable", 0)) {
		editorConfig->ReadFromDirectory(fileDirectory);
	}
}

SA::Colour ColourOfProperty(const PropSetFile &props, const char *key, SA::Colour colourDefault) {
	std::string colour = props.GetExpandedString(key);
	if (!colour.empty()) {
		return ColourFromString(colour);
	}
	return colourDefault;
}

SA::ColourAlpha ColourAlphaOfProperty(const PropSetFile &props, const char *key, SA::ColourAlpha colourDefault) {
	std::string colour = props.GetExpandedString(key);
	if (!colour.empty()) {
		return ColourAlphaFromString(colour);
	}
	return colourDefault;
}

namespace {

void OptionalSetColour(GUI::ScintillaWindow &scintilla, SA::Element element, const PropSetFile &props, const char *key) {
	std::string colour = props.GetExpandedString(key);
	if (!colour.empty()) {
		const SA::Colour colourElement = ColourAlphaFromString(colour);
		scintilla.SetElementColour(element, colourElement);
	} else {
		scintilla.ResetElementColour(element);
	}
}

std::string Join(std::string_view prefix, std::string_view sv, std::string_view suffix) {
	std::string ret(prefix);
	ret += sv;
	ret += suffix;
	return ret;
}

constexpr std::string_view stylePrefix = "style.";

std::string StyleName(std::string_view language, int style) {
	std::string ret(stylePrefix);
	ret += language;
	ret += ".";
	ret += std::to_string(style);
	return ret;
}

std::string StyleName(std::string_view language, int style, int subStyle) {
	std::string base = StyleName(language, style);
	return base + "." + std::to_string(subStyle);
}

}

void SciTEBase::SetElementColour(SA::Element element, const char *key) {
	OptionalSetColour(wEditor, element, props, key);
	OptionalSetColour(wOutput, element, props, key);
}

StyleDefinition SciTEBase::StyleDefinitionFor(int style) {
	const std::string ssDefault = props.GetExpandedString(StyleName("*", style));
	std::string ss = props.GetExpandedString(StyleName(language, style));

	if (!subStyleBases.empty()) {
		const int baseStyle = wEditor.StyleFromSubStyle(style);
		if (baseStyle != style) {
			const int primaryStyle = wEditor.PrimaryStyleFromStyle(style);
			const int distanceSecondary = (style == primaryStyle) ? 0 : wEditor.DistanceToSecondaryStyles();
			const int primaryBase = baseStyle - distanceSecondary;
			const int subStylesStart = wEditor.SubStylesStart(primaryBase);
			const int subStylesLength = wEditor.SubStylesLength(primaryBase);
			const int subStyle = style - (subStylesStart + distanceSecondary);
			if (subStyle < subStylesLength) {
				const std::string key = StyleName(language, baseStyle, subStyle + 1);
				ss = props.GetExpandedString(key);
			}
		}
	}

	StyleDefinition sd(ssDefault);
	sd.ParseStyleDefinition(ss);
	return sd;
}

void SciTEBase::SetOneStyle(GUI::ScintillaWindow &win, int style, std::string_view definition) {
	const StyleDefinition sd(definition);
	if (sd.specified & StyleDefinition::sdItalics)
		win.StyleSetItalic(style, sd.italics);
	if (sd.specified & StyleDefinition::sdWeight)
		win.StyleSetWeight(style, sd.weight);
	if (sd.specified & StyleDefinition::sdStretch)
		win.StyleSetStretch(style, sd.stretch);
	if (sd.specified & StyleDefinition::sdFont) {
		win.StyleSetFont(style, sd.font.c_str());
		bool inMonospacedList = !monospacedList.empty() && (monospacedList.back() == "*");
		if (!inMonospacedList) {
			inMonospacedList = std::ranges::find(monospacedList, sd.font) != monospacedList.end();
		}
		win.StyleSetCheckMonospaced(style, inMonospacedList);
	}
	if (sd.specified & StyleDefinition::sdFore)
		win.StyleSetFore(style, sd.Fore());
	if (sd.specified & StyleDefinition::sdBack)
		win.StyleSetBack(style, sd.Back());
	if (sd.specified & StyleDefinition::sdSize)
		win.StyleSetSizeFractional(style, sd.FractionalSize());
	if (sd.specified & StyleDefinition::sdEOLFilled)
		win.StyleSetEOLFilled(style, sd.eolfilled);
	if (sd.specified & StyleDefinition::sdUnderlined)
		win.StyleSetUnderline(style, sd.underlined);
	if (sd.specified & StyleDefinition::sdCaseForce)
		win.StyleSetCase(style, sd.caseForce);
	if (sd.specified & StyleDefinition::sdVisible)
		win.StyleSetVisible(style, sd.visible);
	if (sd.specified & StyleDefinition::sdChangeable)
		win.StyleSetChangeable(style, sd.changeable);
	if (sd.specified & StyleDefinition::sdInvisibleRep)
		win.StyleSetInvisibleRepresentation(style, sd.invisibleRep.c_str());
	win.StyleSetCharacterSet(style, characterSet);
}

void SciTEBase::SetStyleBlock(GUI::ScintillaWindow &win, const char *lang, int start, int last) {
	for (int style = start; style <= last; style++) {
		if (style != StyleDefault) {
			const std::string key = StyleName(lang, style-start);
			std::string sval = props.GetExpandedString(key);
			if (!sval.empty()) {
				SetOneStyle(win, style, sval);
			}
		}
	}
}

void SciTEBase::SetStyleFor(GUI::ScintillaWindow &win, const char *lang) {
	SetStyleBlock(win, lang, 0, StyleMax);
}

void SciTEBase::SetOneIndicator(GUI::ScintillaWindow &win, SA::IndicatorNumbers indicator, const IndicatorDefinition &ind) {
	const int indic = static_cast<int>(indicator);
	win.IndicSetStyle(indic, ind.style);
	win.IndicSetFore(indic, ind.colour);
	win.IndicSetAlpha(indic, ind.fillAlpha);
	win.IndicSetOutlineAlpha(indic, ind.outlineAlpha);
	win.IndicSetUnder(indic, ind.under);
}

void SciTEBase::SetIndicatorFromProperty(GUI::ScintillaWindow &win, SA::IndicatorNumbers indicator, const std::string &propertyName) {
	const std::string indicatorString = props.GetExpandedString(propertyName);
	if (!indicatorString.empty()) {
		const IndicatorDefinition modifiedIndicator(indicatorString);
		SetOneIndicator(win, indicator, modifiedIndicator);
	}
}

void SciTEBase::SetMarkerFromProperty(GUI::ScintillaWindow &win, int marker, const std::string &propertyName) {
	const std::string markerString = props.GetExpandedString(propertyName);
	if (!markerString.empty()) {
		const MarkerDefinition markerValue(markerString);
		win.MarkerDefine(marker, markerValue.style);
		win.MarkerSetForeTranslucent(marker, markerValue.colour);
		win.MarkerSetBackTranslucent(marker, markerValue.back);
	}
}

std::string SciTEBase::ExtensionFileName() const {
	if (!CurrentBufferConst()->overrideExtension.empty()) {
		return CurrentBufferConst()->overrideExtension;
	}
	const FilePath name = FileNameExt();
	if (name.IsSet()) {
#if !defined(GTK)
		// Force extension to lower case
		std::string extension = name.Extension().AsUTF8();
		if (extension.empty()) {
			return name.AsUTF8();
		}
		LowerCaseAZ(extension);
		return name.BaseName().AsUTF8() + "." + extension;
#else
		return name.AsUTF8();
#endif
	}
	return props.GetString("default.file.ext");
}

void SciTEBase::ForwardPropertyToEditor(const char *key) {
	if (props.Exists(key)) {
		std::string value = props.GetExpandedString(key);
		wEditor.SetProperty(key, value.c_str());
		wOutput.SetProperty(key, value.c_str());
	}
}

void SciTEBase::DefineMarker(SA::MarkerOutline marker, SA::MarkerSymbol markerType, MarkerAppearance markerAppearance) {
	const int markerNumber = static_cast<int>(marker);
	wEditor.MarkerDefine(markerNumber, markerType);
	wEditor.MarkerSetForeTranslucent(markerNumber, markerAppearance.fore);
	wEditor.MarkerSetBackTranslucent(markerNumber, markerAppearance.back);
	wEditor.MarkerSetBackSelectedTranslucent(markerNumber, markerAppearance.backSelected);
	wEditor.MarkerSetStrokeWidth(markerNumber, markerAppearance.strokeWidth);
}

void SciTEBase::ReadAPI(const std::string &fileNameForExtension) {
	std::string sApiFileNames = props.GetNewExpandString("api.",
				    fileNameForExtension);
	if (!sApiFileNames.empty()) {
		std::vector<std::string> vApiFileNames = StringSplit(sApiFileNames, ';');
		std::string data;

		// Load files into data
		for (const std::string &vApiFileName : vApiFileNames) {
			std::string contents = FilePath(GUI::StringFromUTF8(vApiFileName)).Read();
			data.append(contents);
		}

		// Initialise apis
		if (!data.empty()) {
			apis.Set(data);
		}
	}
}

std::string SciTEBase::FindLanguageProperty(const char *pattern, const char *defaultValue) {
	std::string key = pattern;
	Substitute(key, "*", language);
	std::string ret = props.GetExpandedString(key);
	if (ret.empty())
		ret = props.GetExpandedString(pattern);
	if (ret.empty())
		ret = defaultValue;
	return ret;
}

namespace {

/**
 * A list of all the properties that should be forwarded to Scintilla lexers.
 */
const char *propertiesToForward[] = {
	"fold.scintillua.by.indentation",
	"fold.scintillua.line.groups",
	"fold.scintillua.on.zero.sum.lines",
	"fold.scintillua.compact",
//++Autogenerated -- run ../scripts/RegenerateSource.py to regenerate
//**\(\t"\*",\n\)
	"asp.default.language",
	"fold",
	"fold.abl.comment.multiline",
	"fold.abl.syntax.based",
	"fold.asm.comment.explicit",
	"fold.asm.comment.multiline",
	"fold.asm.explicit.anywhere",
	"fold.asm.explicit.end",
	"fold.asm.explicit.start",
	"fold.asm.syntax.based",
	"fold.at.else",
	"fold.baan.inner.level",
	"fold.baan.keywords.based",
	"fold.baan.sections",
	"fold.baan.syntax.based",
	"fold.basic.comment.explicit",
	"fold.basic.explicit.anywhere",
	"fold.basic.explicit.end",
	"fold.basic.explicit.start",
	"fold.basic.syntax.based",
	"fold.cil.comment.multiline",
	"fold.coffeescript.comment",
	"fold.comment",
	"fold.comment.nimrod",
	"fold.comment.yaml",
	"fold.compact",
	"fold.cpp.comment.explicit",
	"fold.cpp.comment.multiline",
	"fold.cpp.explicit.anywhere",
	"fold.cpp.explicit.end",
	"fold.cpp.explicit.start",
	"fold.cpp.preprocessor.at.else",
	"fold.cpp.syntax.based",
	"fold.d.comment.explicit",
	"fold.d.comment.multiline",
	"fold.d.explicit.anywhere",
	"fold.d.explicit.end",
	"fold.d.explicit.start",
	"fold.d.syntax.based",
	"fold.dataflex.compilerlist",
	"fold.directive",
	"fold.fsharp.comment.multiline",
	"fold.fsharp.comment.stream",
	"fold.fsharp.imports",
	"fold.fsharp.preprocessor",
	"fold.fsharp.quotes",
	"fold.gdscript.quotes",
	"fold.haskell.imports",
	"fold.html",
	"fold.html.preprocessor",
	"fold.hypertext.comment",
	"fold.hypertext.heredoc",
	"fold.julia.docstring",
	"fold.julia.syntax.based",
	"fold.perl.at.else",
	"fold.perl.comment.explicit",
	"fold.perl.package",
	"fold.perl.pod",
	"fold.preprocessor",
	"fold.quotes.nimrod",
	"fold.quotes.python",
	"fold.raku.comment.multiline",
	"fold.raku.comment.pod",
	"fold.rust.comment.explicit",
	"fold.rust.comment.multiline",
	"fold.rust.explicit.anywhere",
	"fold.rust.explicit.end",
	"fold.rust.explicit.start",
	"fold.rust.syntax.based",
	"fold.sql.at.else",
	"fold.sql.only.begin",
	"fold.verilog.flags",
	"fold.xml.at.tag.open",
	"html.tags.case.sensitive",
	"lexer.as.comment.character",
	"lexer.asm.comment.delimiter",
	"lexer.baan.styling.within.preprocessor",
	"lexer.bash.command.substitution",
	"lexer.bash.nested.backticks",
	"lexer.bash.special.parameter",
	"lexer.bash.styling.inside.backticks",
	"lexer.bash.styling.inside.heredoc",
	"lexer.bash.styling.inside.parameter",
	"lexer.bash.styling.inside.string",
	"lexer.caml.magic",
	"lexer.cpp.allow.dollars",
	"lexer.cpp.allow.hashes",
	"lexer.cpp.backquoted.strings",
	"lexer.cpp.enable.preprocessor",
	"lexer.cpp.escape.sequence",
	"lexer.cpp.hashquoted.strings",
	"lexer.cpp.track.preprocessor",
	"lexer.cpp.triplequoted.strings",
	"lexer.cpp.update.preprocessor",
	"lexer.cpp.verbatim.strings.allow.escapes",
	"lexer.css.hss.language",
	"lexer.css.less.language",
	"lexer.css.scss.language",
	"lexer.d.fold.at.else",
	"lexer.edifact.highlight.un.all",
	"lexer.errorlist.escape.sequences",
	"lexer.errorlist.value.separate",
	"lexer.escseq.colour.text",
	"lexer.flagship.styling.within.preprocessor",
	"lexer.gdscript.keywords2.no.sub.identifiers",
	"lexer.gdscript.literals.binary",
	"lexer.gdscript.strings.over.newline",
	"lexer.gdscript.unicode.identifiers",
	"lexer.gdscript.whinge.level",
	"lexer.haskell.allow.hash",
	"lexer.haskell.allow.questionmark",
	"lexer.haskell.allow.quotes",
	"lexer.haskell.cpp",
	"lexer.haskell.import.safe",
	"lexer.html.allow.asp",
	"lexer.html.allow.php",
	"lexer.html.django",
	"lexer.html.mako",
	"lexer.json.allow.comments",
	"lexer.json.escape.sequence",
	"lexer.julia.highlight.lexerror",
	"lexer.julia.highlight.typeannotation",
	"lexer.markdown.header.eolfill",
	"lexer.metapost.comment.process",
	"lexer.metapost.interface.default",
	"lexer.nim.raw.strings.highlight.ident",
	"lexer.pascal.smart.highlighting",
	"lexer.props.allow.initial.spaces",
	"lexer.python.decorator.attributes",
	"lexer.python.identifier.attributes",
	"lexer.python.keywords2.no.sub.identifiers",
	"lexer.python.literals.binary",
	"lexer.python.strings.b",
	"lexer.python.strings.f",
	"lexer.python.strings.f.pep.701",
	"lexer.python.strings.over.newline",
	"lexer.python.strings.t",
	"lexer.python.strings.u",
	"lexer.python.unicode.identifiers",
	"lexer.r.escape.sequence",
	"lexer.rust.fold.at.else",
	"lexer.sql.allow.dotted.word",
	"lexer.sql.backticks.identifier",
	"lexer.sql.numbersign.comment",
	"lexer.tex.auto.if",
	"lexer.tex.comment.process",
	"lexer.tex.interface.default",
	"lexer.tex.use.keywords",
	"lexer.vb.strings.multiline",
	"lexer.verilog.allupperkeywords",
	"lexer.verilog.fold.preprocessor.else",
	"lexer.verilog.portstyling",
	"lexer.verilog.track.preprocessor",
	"lexer.verilog.update.preprocessor",
	"lexer.visualprolog.backquoted.strings",
	"lexer.visualprolog.verbatim.strings",
	"lexer.xml.allow.asp",
	"lexer.xml.allow.php",
	"lexer.xml.allow.scripts",
	"nsis.ignorecase",
	"nsis.uservars",
	"ps.level",
	"sql.backslash.escapes",
	"styling.within.preprocessor",
	"tab.timmy.whinge.level",

//--Autogenerated -- end of automatically generated section

	nullptr,
};

/* XPM */
const char *bookmarkBluegem[] = {
/* width height num_colors chars_per_pixel */
"    15    15      64            1",
/* colors */
"  c none",
". c #0c0630",
"# c #8c8a8c",
"a c #244a84",
"b c #545254",
"c c #cccecc",
"d c #949594",
"e c #346ab4",
"f c #242644",
"g c #3c3e3c",
"h c #6ca6fc",
"i c #143789",
"j c #204990",
"k c #5c8dec",
"l c #707070",
"m c #3c82dc",
"n c #345db4",
"o c #619df7",
"p c #acacac",
"q c #346ad4",
"r c #1c3264",
"s c #174091",
"t c #5482df",
"u c #4470c4",
"v c #2450a0",
"w c #14162c",
"x c #5c94f6",
"y c #b7b8b7",
"z c #646464",
"A c #3c68b8",
"B c #7cb8fc",
"C c #7c7a7c",
"D c #3462b9",
"E c #7c7eac",
"F c #44464c",
"G c #a4a4a4",
"H c #24224c",
"I c #282668",
"J c #5c5a8c",
"K c #7c8ebc",
"L c #dcd7e4",
"M c #141244",
"N c #1c2e5c",
"O c #24327c",
"P c #4472cc",
"Q c #6ca2fc",
"R c #74b2fc",
"S c #24367c",
"T c #b4b2c4",
"U c #403e58",
"V c #4c7fd6",
"W c #24428c",
"X c #747284",
"Y c #142e7c",
"Z c #64a2fc",
"0 c #3c72dc",
"1 c #bcbebc",
"2 c #6c6a6c",
"3 c #848284",
"4 c #2c5098",
"5 c #1c1a1c",
"6 c #243250",
"7 c #7cbefc",
"8 c #d4d2d4",
/* pixels */
"    yCbgbCy    ",
"   #zGGyGGz#   ",
"  #zXTLLLTXz#  ",
" p5UJEKKKEJU5p ",
" lfISa444aSIfl ",
" wIYij444jsYIw ",
" .OsvnAAAnvsO. ",
" MWvDuVVVPDvWM ",
" HsDPVkxxtPDsH ",
" UiAtxohZxtuiU ",
" pNnkQRBRhkDNp ",
" 1FrqoR7Bo0rF1 ",
" 8GC6aemea6CG8 ",
"  cG3l2z2l3Gc  ",
"    1GdddG1    "
};

}

std::string SciTEBase::GetFileNameProperty(const char *name) {
	std::string namePlusDot = name;
	namePlusDot.append(".");
	std::string valueForFileName = props.GetNewExpandString(namePlusDot,
				       ExtensionFileName());
	if (!valueForFileName.empty()) {
		return valueForFileName;
	}
	return props.GetString(name);
}

void SciTEBase::SetRepresentations() {
	wEditor.ClearAllRepresentations();
	const std::string representations = props.GetExpandedString("representations");
	std::string_view reprs = representations;

	bool colourSet = false;
	SA::Colour colourRepresentation = 0;

	bool appearanceSet = false;
	SA::RepresentationAppearance appearanceRepresentation = SA::RepresentationAppearance::Blob;

	while (!reprs.empty()) {
		if (reprs[0] == ',') {
			reprs.remove_prefix(1);
		}
		if (!reprs.empty()) {
			const std::string_view item = reprs.substr(0, reprs.find_first_of(','));
			if (!item.empty()) {
				if (item[0] == '!') {
					// Appearance
					if (item.length() == 1) {
						appearanceSet = false;
					} else {
						appearanceSet = true;
						const std::string sAppearance(item.substr(1));
						appearanceRepresentation = static_cast<SA::RepresentationAppearance>(IntegerFromString(sAppearance, 0));
					}
				} else if (item[0] == '#') {
					// Colour
					if (item.length() == 1) {
						colourSet = false;
					} else {
						colourSet = true;
						colourRepresentation = ColourAlphaFromString(item);
					}
				} else {
					const size_t eqPos = item.find_first_of('=');
					if (eqPos != std::string_view::npos) {
						const std::string target = UnicodeUnEscape(item.substr(0, eqPos));
						const std::string representation = UnicodeUnEscape(item.substr(eqPos+1));
						wEditor.SetRepresentation(target.c_str(), representation.c_str());
						if (appearanceSet) {
							wEditor.SetRepresentationAppearance(target.c_str(), appearanceRepresentation);
						}
						if (colourSet) {
							wEditor.SetRepresentationColour(target.c_str(), colourRepresentation);
						}
					}
				}
				reprs.remove_prefix(item.length());
			}
		}
	}
}

void SciTEBase::ReadProperties() {
	if (extender)
		extender->Clear();

	const std::string lexillaPath = props.GetExpandedString("lexilla.path");
	Lexilla::Load(lexillaPath.empty() ? "." : lexillaPath);

	std::vector<std::string> libraryProperties = Lexilla::LibraryProperties();
	for (const std::string &property : libraryProperties) {
		std::string key("lexilla.context.");
		key += property;
		std::string value = props.GetExpandedString(key);
		Lexilla::SetProperty(property.c_str(), value.c_str());
	}

	const std::string fileNameForExtension = ExtensionFileName();

	language = props.GetNewExpandString("lexer.", fileNameForExtension);
	if (language.empty()) {
		language = "null";
	}
	if (static_cast<int>(wEditor.DocumentOptions()) & static_cast<int>(SA::DocumentOption::StylesNone)) {
		language = "null";
	}
	const std::string languageCurrent = wEditor.LexerLanguage();
	if (language != languageCurrent) {
		if (language.starts_with("script_")) {
			if (propsUser.GetInt("allow.script.lexer")) {
				wEditor.SetILexer(nullptr);
			} else {
				wEditor.SetILexer(Lexilla::MakeLexer("null"));
			}
		} else {
			Scintilla::ILexer5 *plexer = Lexilla::MakeLexer(language);
			wEditor.SetILexer(plexer);
		}
	}

	props.Set("Language", language);

	lexLanguage = wEditor.Lexer();

	const std::string languageOutput = wOutput.LexerLanguage();
	if (languageOutput != "errorlist") {
		Scintilla::ILexer5 *plexerErrorlist = Lexilla::MakeLexer("errorlist");
		wOutput.SetILexer(plexerErrorlist);
	}

	const std::string kw0 = props.GetNewExpandString("keywords.", fileNameForExtension);
	wEditor.SetKeyWords(0, kw0.c_str());

	for (int wl = 1; wl <= SA::KeywordsetMax; wl++) {
		std::string kwk = StdStringFromInteger(wl+1);
		kwk += '.';
		kwk.insert(0, "keywords");
		const std::string kw = props.GetNewExpandString(kwk, fileNameForExtension);
		wEditor.SetKeyWords(wl, kw.c_str());
	}

	subStyleBases = wEditor.SubStyleBases();
	if (!subStyleBases.empty()) {
		wEditor.FreeSubStyles();

		for (const unsigned char subStyleBase : subStyleBases) {
			//substyles.cpp.11=2
			const std::string sStyleBase = StdStringFromInteger(subStyleBase);
			std::string ssSubStylesKey = "substyles.";
			ssSubStylesKey += language;
			ssSubStylesKey += ".";
			ssSubStylesKey += sStyleBase;
			std::string ssNumber = props.GetNewExpandString(ssSubStylesKey);
			int subStyleIdentifiers = IntegerFromString(ssNumber, 0);

			int subStyleIdentifiersStart = 0;
			if (subStyleIdentifiers) {
				subStyleIdentifiersStart = wEditor.AllocateSubStyles(subStyleBase, subStyleIdentifiers);
				if (subStyleIdentifiersStart < 0)
					subStyleIdentifiers = 0;
			}
			for (int subStyle=0; subStyle<subStyleIdentifiers; subStyle++) {
				// substylewords.11.1.$(file.patterns.cpp)=CharacterSet LexAccessor SString WordList
				std::string ssWordsKey = "substylewords.";
				ssWordsKey += sStyleBase;
				ssWordsKey += ".";
				ssWordsKey += StdStringFromInteger(subStyle + 1);
				ssWordsKey += ".";
				std::string ssWords = props.GetNewExpandString(ssWordsKey, fileNameForExtension);
				wEditor.SetIdentifiers(subStyleIdentifiersStart + subStyle, ssWords.c_str());
			}
		}
	}

	props.SetPath("SciteDefaultHome", GetSciteDefaultHome());
	props.SetPath("SciteUserHome", GetSciteUserHome());

	for (size_t i=0; propertiesToForward[i]; i++) {
		ForwardPropertyToEditor(propertiesToForward[i]);
	}

	if (apisFileNames != props.GetNewExpandString("api.", fileNameForExtension)) {
		apis.Clear();
		ReadAPI(fileNameForExtension);
		apisFileNames = props.GetNewExpandString("api.", fileNameForExtension);
	}

	props.Set("APIPath", apisFileNames);

	FilePath fileAbbrev = GUI::StringFromUTF8(props.GetNewExpandString("abbreviations.", fileNameForExtension));
	if (!fileAbbrev.IsSet())
		fileAbbrev = GetAbbrevPropertiesFileName();
	if (!pathAbbreviations.SameNameAs(fileAbbrev)) {
		pathAbbreviations = fileAbbrev;
		ReadAbbrevPropFile();
	}

	props.SetPath("AbbrevPath", pathAbbreviations);

	const SA::Technology tech = props.GetEnum("technology", SA::Technology::Default);
	wEditor.SetTechnology(tech);
	wOutput.SetTechnology(tech);

	const SA::Bidirectional bidirectional = props.GetEnum("bidirectional", SA::Bidirectional::Disabled);
	wEditor.SetBidirectional(bidirectional);
	wOutput.SetBidirectional(bidirectional);

	codePage = props.GetInt("code.page");
	if (CurrentBuffer()->unicodeMode != UniMode::uni8Bit) {
		// Override properties file to ensure Unicode displayed.
		codePage = SA::CpUtf8;
	}
	wEditor.SetCodePage(codePage);
	const int outputCodePage = props.GetInt("output.code.page", codePage);
	wOutput.SetCodePage(outputCodePage);

	characterSet = props.GetEnum("character.set", SA::CharacterSet::Default);

	SetRepresentations();

#if defined(__unix__) || defined(__APPLE__)
	const std::string localeCType = props.GetString("LC_CTYPE");
	if (localeCType.length())
		setlocale(LC_CTYPE, localeCType.c_str());
	else
		setlocale(LC_CTYPE, "C");
#endif

	std::string imeInteraction = props.GetString("ime.interaction");
	if (!imeInteraction.empty()) {
		CallChildren(SA::Message::SetIMEInteraction, props.GetInt("ime.interaction", static_cast<int>(SA::IMEInteraction::Windowed)));
	}
	imeAutoComplete = props.GetInt("ime.autocomplete", 0) == 1;

	const SA::Accessibility accessibility = props.GetEnum("accessibility", SA::Accessibility::Enabled);
	wEditor.SetAccessibility(accessibility);
	wOutput.SetAccessibility(accessibility);

	wrapStyle = props.GetEnum("wrap.style", SA::Wrap::Word);

	CallChildren(SA::Message::SetMouseSelectionRectangularSwitch, props.GetInt("selection.rectangular.switch.mouse", 0));
	CallChildren(SA::Message::SetMultipleSelection, props.GetInt("selection.multiple", 1));
	CallChildren(SA::Message::SetAdditionalSelectionTyping, props.GetInt("selection.additional.typing", 1));
	CallChildren(SA::Message::SetMultiPaste, props.GetInt("selection.multipaste", 1));
	CallChildren(SA::Message::SetAdditionalCaretsBlink, props.GetInt("caret.additional.blinks", 1));
	CallChildren(SA::Message::SetVirtualSpaceOptions, props.GetInt("virtual.space"));

	wEditor.SetMouseDwellTime(props.GetInt("dwell.period", SA::TimeForever));

	SetElementColour(SA::Element::Caret, "caret.fore");
	SetElementColour(SA::Element::CaretAdditional, "caret.additional.fore");

	const SA::CaretStyle caretStyle = props.GetEnum("caret.style", SA::CaretStyle::Line);
	wEditor.SetCaretStyle(caretStyle);
	wOutput.SetCaretStyle(caretStyle);
	wEditor.SetCaretWidth(props.GetInt("caret.width", 1));
	wOutput.SetCaretWidth(props.GetInt("caret.width", 1));

	const std::string caretLineLayer = props.GetExpandedString("caret.line.layer");
	if (caretLineLayer.empty()) {
		std::string caretLineBack = props.GetExpandedString("caret.line.back");
		if (!caretLineBack.empty()) {
			wEditor.SetCaretLineVisible(true);
			wEditor.SetCaretLineBack(ColourFromString(caretLineBack));
		} else {
			wEditor.SetCaretLineVisible(false);
		}
		wEditor.SetCaretLineBackAlpha(
			props.GetEnum("caret.line.back.alpha", SA::Alpha::NoAlpha));
	} else {
		// New scheme
		const int layer = IntegerFromString(caretLineLayer, 0);
		CallChildren(SA::Message::SetCaretLineLayer, layer);
		SetElementColour(SA::Element::CaretLineBack, "caret.line.back");
		const int caretLineFrame = props.GetInt("caret.line.frame");
		CallChildren(SA::Message::SetCaretLineFrame, caretLineFrame);
	}

	int indicatorsAlpha = props.GetInt("indicators.alpha", 30);
	if (indicatorsAlpha < 0 || 255 < indicatorsAlpha) // If invalid value,
		indicatorsAlpha = 30; //then set default value.
	alphaIndicator = static_cast<SA::Alpha>(indicatorsAlpha);
	underIndicator = props.GetInt("indicators.under", 0) == 1;

	closeFind = props.GetEnum("find.close.on.find", CloseFind::closeAlways);

	const std::string controlCharSymbol = props.GetString("control.char.symbol");
	if (!controlCharSymbol.empty()) {
		wEditor.SetControlCharSymbol(static_cast<unsigned char>(controlCharSymbol[0]));
	} else {
		wEditor.SetControlCharSymbol(0);
	}

	const std::string caretPeriod = props.GetString("caret.period");
	if (!caretPeriod.empty()) {
		const int caretPeriodValue = IntegerFromString(caretPeriod, 0);
		wEditor.SetCaretPeriod(caretPeriodValue);
		wOutput.SetCaretPeriod(caretPeriodValue);
	}

	const int caretZoneX = props.GetInt("caret.policy.width", 50);
	int caretPolicyX = 0;
	if (props.GetInt("caret.policy.xslop", 1))
		caretPolicyX |= static_cast<int>(SA::CaretPolicy::Slop);
	if (props.GetInt("caret.policy.xstrict"))
		caretPolicyX |= static_cast<int>(SA::CaretPolicy::Strict);
	if (props.GetInt("caret.policy.xeven", 1))
		caretPolicyX |= static_cast<int>(SA::CaretPolicy::Even);
	if (props.GetInt("caret.policy.xjumps"))
		caretPolicyX |= static_cast<int>(SA::CaretPolicy::Jumps);
	//wEditor.SetXCaretPolicy(caretStrict | caretSlop | caretEven | caretJumps);
	wEditor.SetXCaretPolicy(static_cast<SA::CaretPolicy>(caretPolicyX), caretZoneX);

	const int caretZoneY = props.GetInt("caret.policy.lines");
	int caretPolicyY = 0;
	if (props.GetInt("caret.policy.yslop", 1))
		caretPolicyY |= static_cast<int>(SA::CaretPolicy::Slop);
	if (props.GetInt("caret.policy.ystrict"))
		caretPolicyY |= static_cast<int>(SA::CaretPolicy::Strict);
	if (props.GetInt("caret.policy.yeven", 1))
		caretPolicyY |= static_cast<int>(SA::CaretPolicy::Even);
	if (props.GetInt("caret.policy.yjumps"))
		caretPolicyY |= static_cast<int>(SA::CaretPolicy::Jumps);
	wEditor.SetYCaretPolicy(static_cast<SA::CaretPolicy>(caretPolicyY), caretZoneY);

	int visiblePolicy = 0;
	if (props.GetInt("visible.policy.strict"))
		visiblePolicy |= static_cast<int>(SA::VisiblePolicy::Strict);
	if (props.GetInt("visible.policy.slop", 1))
		visiblePolicy |= static_cast<int>(SA::VisiblePolicy::Slop);
	const int visibleLines = props.GetInt("visible.policy.lines");
	wEditor.SetVisiblePolicy(static_cast<SA::VisiblePolicy>(visiblePolicy), visibleLines);

	wEditor.SetEdgeColumn(props.GetInt("edge.column", 0));
	wEditor.SetEdgeMode(props.GetEnum("edge.mode", SA::EdgeVisualStyle::None));
	wEditor.SetEdgeColour(
		ColourOfProperty(props, "edge.colour", ColourRGB(0xff, 0xda, 0xda)));

	const std::string selectionLayer = props.GetExpandedString("selection.layer");
	if (selectionLayer.empty()) {

		std::string selFore = props.GetExpandedString("selection.fore");
		if (!selFore.empty()) {
			CallChildren(SA::Message::SetSelFore, 1, ColourFromString(selFore));
		} else {
			CallChildren(SA::Message::SetSelFore, 0, 0);
		}
		std::string selBack = props.GetExpandedString("selection.back");
		if (!selBack.empty()) {
			CallChildren(SA::Message::SetSelBack, 1, ColourFromString(selBack));
		} else {
			if (!selFore.empty())
				CallChildren(SA::Message::SetSelBack, 0, 0);
			else	// Have to show selection somehow
				CallChildren(SA::Message::SetSelBack, 1, ColourRGB(0xC0, 0xC0, 0xC0));
		}
		constexpr int NoAlpha = static_cast<int>(SA::Alpha::NoAlpha);
		const int selectionAlpha = props.GetInt("selection.alpha", NoAlpha);
		CallChildren(SA::Message::SetSelAlpha, selectionAlpha);

		std::string selAdditionalFore = props.GetString("selection.additional.fore");
		if (!selAdditionalFore.empty()) {
			CallChildren(SA::Message::SetAdditionalSelFore, ColourFromString(selAdditionalFore));
		}
		std::string selAdditionalBack = props.GetString("selection.additional.back");
		if (!selAdditionalBack.empty()) {
			CallChildren(SA::Message::SetAdditionalSelBack, ColourFromString(selAdditionalBack));
		}
		const int selectionAdditionalAlpha = (selectionAlpha == NoAlpha) ? NoAlpha : selectionAlpha / 2;
		CallChildren(SA::Message::SetAdditionalSelAlpha, props.GetInt("selection.additional.alpha", selectionAdditionalAlpha));

	} else {
		// New scheme
		const int layer = IntegerFromString(selectionLayer, 0);
		CallChildren(SA::Message::SetSelectionLayer, layer);
		SetElementColour(SA::Element::SelectionText, "selection.fore");
		SetElementColour(SA::Element::SelectionBack, "selection.back");
		SetElementColour(SA::Element::SelectionAdditionalText, "selection.additional.fore");
		SetElementColour(SA::Element::SelectionAdditionalBack, "selection.additional.back");
		SetElementColour(SA::Element::SelectionSecondaryText, "selection.secondary.fore");
		SetElementColour(SA::Element::SelectionSecondaryBack, "selection.secondary.back");
		SetElementColour(SA::Element::SelectionInactiveText, "selection.inactive.fore");
		SetElementColour(SA::Element::SelectionInactiveBack, "selection.inactive.back");
	}

	foldColour = props.GetExpandedString("fold.margin.colour");
	if (!foldColour.empty()) {
		CallChildren(SA::Message::SetFoldMarginColour, 1, ColourFromString(foldColour));
	} else {
		CallChildren(SA::Message::SetFoldMarginColour, 0, 0);
	}
	foldHiliteColour = props.GetExpandedString("fold.margin.highlight.colour");
	if (!foldHiliteColour.empty()) {
		CallChildren(SA::Message::SetFoldMarginHiColour, 1, ColourFromString(foldHiliteColour));
	} else {
		CallChildren(SA::Message::SetFoldMarginHiColour, 0, 0);
	}

	SetElementColour(SA::Element::WhiteSpace, "whitespace.fore");
	SetElementColour(SA::Element::WhiteSpaceBack, "whitespace.back");

	CallChildren(SA::Message::SetWhitespaceSize, props.GetInt("whitespace.size", 1));

	bracesStyle = props.GetInt(Join("braces.", language, ".style"), 0);

	std::string sval;

	sval = FindLanguageProperty("calltip.*.ignorecase");
	callTipIgnoreCase = sval == "1";
	sval = FindLanguageProperty("calltip.*.use.escapes");
	callTipUseEscapes = sval == "1";

	calltipWordCharacters = FindLanguageProperty("calltip.*.word.characters",
				"_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
	calltipParametersStart = FindLanguageProperty("calltip.*.parameters.start", "(");
	calltipParametersEnd = FindLanguageProperty("calltip.*.parameters.end", ")");
	calltipParametersSeparators = FindLanguageProperty("calltip.*.parameters.separators", ",;");

	calltipEndDefinition = FindLanguageProperty("calltip.*.end.definition");

	autoCompleteStartCharacters = FindLanguageProperty("autocomplete.*.start.characters");
	// "" is a quite reasonable value for this setting

	autoCompleteFillUpCharacters = FindLanguageProperty("autocomplete.*.fillups");
	wEditor.AutoCSetFillUps(autoCompleteFillUpCharacters.c_str());

	autoCompleteTypeSeparator = FindLanguageProperty("autocomplete.*.typesep");
	if (!autoCompleteTypeSeparator.empty()) {
		wEditor.AutoCSetTypeSeparator(
			static_cast<unsigned char>(autoCompleteTypeSeparator[0]));
	}

	sval = FindLanguageProperty("autocomplete.*.ignorecase");
	autoCompleteIgnoreCase = sval == "1";
	wEditor.AutoCSetIgnoreCase(autoCompleteIgnoreCase);
	wOutput.AutoCSetIgnoreCase(true);
	autoCompleteVisibleItemCount = props.GetInt("autocomplete.visible.item.count", 9);

	const int autoCChooseSingle = props.GetInt("autocomplete.choose.single");
	wEditor.AutoCSetChooseSingle(autoCChooseSingle);

	const Scintilla::MultiAutoComplete autoCMulti = props.GetEnum("autocomplete.multi", SA::MultiAutoComplete::Once);
	wEditor.AutoCSetMulti(autoCMulti);

	wEditor.AutoCSetCancelAtStart(false);
	wEditor.AutoCSetDropRestOfWord(false);

	OptionalSetColour(wEditor, SA::Element::List, props, "autocomplete.fore");
	OptionalSetColour(wEditor, SA::Element::ListBack, props, "autocomplete.back");
	OptionalSetColour(wEditor, SA::Element::ListSelected, props, "autocomplete.selected.fore");
	OptionalSetColour(wEditor, SA::Element::ListSelectedBack, props, "autocomplete.selected.back");

	if (firstPropertiesRead) {
		ReadPropertiesInitial();
	}

	ReadFontProperties();

	wEditor.SetPrintMagnification(props.GetInt("print.magnification"));
	wEditor.SetPrintColourMode(props.GetEnum("print.colour.mode", SA::PrintOption::Normal));

	jobQueue.clearBeforeExecute = props.GetInt("clear.before.execute");
	jobQueue.timeCommands = props.GetInt("time.commands");

	const int blankMarginLeft = props.GetInt("blank.margin.left", 1);
	const int blankMarginLeftOutput = props.GetInt("output.blank.margin.left", blankMarginLeft);
	const int blankMarginRight = props.GetInt("blank.margin.right", 1);
	wEditor.SetMarginLeft(blankMarginLeft);
	wEditor.SetMarginRight(blankMarginRight);
	wOutput.SetMarginLeft(blankMarginLeftOutput);
	wOutput.SetMarginRight(blankMarginRight);

	marginWidth = props.GetInt("margin.width");
	if (marginWidth == 0)
		marginWidth = marginWidthDefault;
	wEditor.SetMarginWidthN(1, margin ? marginWidth : 0);

	const std::string lineMarginProp = props.GetString("line.margin.width");
	lineNumbersWidth = IntegerFromString(lineMarginProp, lineNumbersWidthDefault);
	lineNumbersExpand = lineMarginProp.find('+') != std::string::npos;

	SetLineNumberWidth();

	const SA::CursorShape marginCursor = props.GetEnum("margin.cursor", SA::CursorShape::ReverseArrow);
	for (int m=0; m<3; m++) {
		wEditor.SetMarginCursorN(m, marginCursor);
	}

	bufferedDraw = props.GetInt("buffered.draw");
	wEditor.SetBufferedDraw(bufferedDraw);
	wOutput.SetBufferedDraw(bufferedDraw);

	const SA::PhasesDraw phasesDraw = props.GetEnum("phases.draw", SA::PhasesDraw::Two);
	wEditor.SetPhasesDraw(phasesDraw);
	wOutput.SetPhasesDraw(phasesDraw);

	wEditor.SetLayoutCache(props.GetEnum("cache.layout", SA::LineCache::Caret));
	wOutput.SetLayoutCache(props.GetEnum("output.cache.layout", SA::LineCache::Caret));

	wEditor.SetLayoutThreads(props.GetInt("threads.layout", 1));

	bracesCheck = props.GetInt("braces.check");
	bracesSloppy = props.GetInt("braces.sloppy");

	wEditor.SetCharsDefault();
	wordCharacters = props.GetNewExpandString("word.characters.", fileNameForExtension);
	if (!wordCharacters.empty()) {
		wEditor.SetWordChars(wordCharacters.c_str());
	} else {
		wordCharacters = "_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
	}

	whitespaceCharacters = props.GetNewExpandString("whitespace.characters.", fileNameForExtension);
	if (!whitespaceCharacters.empty()) {
		wEditor.SetWhitespaceChars(whitespaceCharacters.c_str());
	}

	const std::string viewIndentExamine = GetFileNameProperty("view.indentation.examine");
	indentExamine = static_cast<SA::IndentView>(IntegerFromString(viewIndentExamine, 1));
	wEditor.SetIndentationGuides(props.GetInt("view.indentation.guides") ?
				     indentExamine : SA::IndentView::None);

	wEditor.SetTabIndents(props.GetInt("tab.indents", 1));
	wEditor.SetBackSpaceUnIndents(props.GetInt("backspace.unindents", 1));

	wEditor.CallTipUseStyle(32);

	std::string useStripTrailingSpaces = props.GetNewExpandString("strip.trailing.spaces.", ExtensionFileName());
	stripTrailingSpaces = IntegerFromString(useStripTrailingSpaces, props.GetInt("strip.trailing.spaces")) != 0;

	ensureFinalLineEnd = props.GetInt("ensure.final.line.end") != 0;
	ensureConsistentLineEnds = props.GetInt("ensure.consistent.line.ends") != 0;

	indentOpening = props.GetInt("indent.opening");
	indentClosing = props.GetInt("indent.closing");
	indentMaintain = IntegerFromString(props.GetNewExpandString("indent.maintain.", fileNameForExtension), 0);

	const std::string lookback = props.GetNewExpandString("statement.lookback.", fileNameForExtension);
	statementLookback = IntegerFromString(lookback, 0);
	statementIndent = GetStyleAndWords("statement.indent.");
	statementEnd = GetStyleAndWords("statement.end.");
	blockStart = GetStyleAndWords("block.start.");
	blockEnd = GetStyleAndWords("block.end.");

	struct PropToPPC {
		const char *propName;
		PreProc ppc;
	};
	const PropToPPC propToPPC[] = {
		{"preprocessor.start.", PreProc::Start},
		{"preprocessor.middle.", PreProc::Middle},
		{"preprocessor.end.", PreProc::End},
	};
	const std::string ppSymbol = props.GetNewExpandString("preprocessor.symbol.", fileNameForExtension);
	preprocessorSymbol = ppSymbol.empty() ? 0 : ppSymbol[0];
	preprocOfString.clear();
	for (const PropToPPC &preproc : propToPPC) {
		const std::string list = props.GetNewExpandString(preproc.propName, fileNameForExtension);
		const std::vector<std::string> words = StringSplit(list, ' ');
		for (const std::string &word : words) {
			preprocOfString[word] = preproc.ppc;
		}
	}

	const std::vector<std::string> fileSets = StringSplit(props.GetNewExpandString("find.files"), '|');
	memFiles.AppendList(fileSets);

	wEditor.SetWrapVisualFlags(props.GetEnum("wrap.visual.flags", SA::WrapVisualFlag::None));
	wEditor.SetWrapVisualFlagsLocation(props.GetEnum("wrap.visual.flags.location", SA::WrapVisualLocation::Default));
	wEditor.SetWrapStartIndent(props.GetInt("wrap.visual.startindent"));
	wEditor.SetWrapIndentMode(props.GetEnum("wrap.indent.mode", SA::WrapIndentMode::Fixed));

	idleStyling = props.GetEnum("idle.styling", SA::IdleStyling::None);
	wEditor.SetIdleStyling(idleStyling);
	wOutput.SetIdleStyling(props.GetEnum("output.idle.styling", SA::IdleStyling::None));

	if (props.GetInt("os.x.home.end.keys")) {
		AssignKey(SA::Keys::Home, SA::KeyMod::Norm, SCI_SCROLLTOSTART);
		AssignKey(SA::Keys::Home, SA::KeyMod::Shift, SCI_NULL);
		AssignKey(SA::Keys::Home, SA::KeyMod::Shift | SA::KeyMod::Alt, SCI_NULL);
		AssignKey(SA::Keys::End, SA::KeyMod::Norm, SCI_SCROLLTOEND);
		AssignKey(SA::Keys::End, SA::KeyMod::Shift, SCI_NULL);
	} else {
		if (props.GetInt("wrap.aware.home.end.keys", 0)) {
			if (props.GetInt("vc.home.key", 1)) {
				AssignKey(SA::Keys::Home, SA::KeyMod::Norm, SCI_VCHOMEWRAP);
				AssignKey(SA::Keys::Home, SA::KeyMod::Shift, SCI_VCHOMEWRAPEXTEND);
				AssignKey(SA::Keys::Home, SA::KeyMod::Shift | SA::KeyMod::Alt, SCI_VCHOMERECTEXTEND);
			} else {
				AssignKey(SA::Keys::Home, SA::KeyMod::Norm, SCI_HOMEWRAP);
				AssignKey(SA::Keys::Home, SA::KeyMod::Shift, SCI_HOMEWRAPEXTEND);
				AssignKey(SA::Keys::Home, SA::KeyMod::Shift | SA::KeyMod::Alt, SCI_HOMERECTEXTEND);
			}
			AssignKey(SA::Keys::End, SA::KeyMod::Norm, SCI_LINEENDWRAP);
			AssignKey(SA::Keys::End, SA::KeyMod::Shift, SCI_LINEENDWRAPEXTEND);
		} else {
			if (props.GetInt("vc.home.key", 1)) {
				AssignKey(SA::Keys::Home, SA::KeyMod::Norm, SCI_VCHOME);
				AssignKey(SA::Keys::Home, SA::KeyMod::Shift, SCI_VCHOMEEXTEND);
				AssignKey(SA::Keys::Home, SA::KeyMod::Shift | SA::KeyMod::Alt, SCI_VCHOMERECTEXTEND);
			} else {
				AssignKey(SA::Keys::Home, SA::KeyMod::Norm, SCI_HOME);
				AssignKey(SA::Keys::Home, SA::KeyMod::Shift, SCI_HOMEEXTEND);
				AssignKey(SA::Keys::Home, SA::KeyMod::Shift | SA::KeyMod::Alt, SCI_HOMERECTEXTEND);
			}
			AssignKey(SA::Keys::End, SA::KeyMod::Norm, SCI_LINEEND);
			AssignKey(SA::Keys::End, SA::KeyMod::Shift, SCI_LINEENDEXTEND);
		}
	}

	AssignKey(static_cast<SA::Keys>('L'), SA::KeyMod::Shift | SA::KeyMod::Ctrl, SCI_LINEDELETE);


	scrollOutput = props.GetInt("output.scroll", 1);

	tabHideOne = props.GetInt("tabbar.hide.one");

	SetToolsMenu();

	wEditor.SetFoldFlags(props.GetEnum("fold.flags", SA::FoldFlag::None));

	// To put the folder markers in the line number region
	//wEditor.SetMarginMaskN(0, SC_MASK_FOLDERS);

	wEditor.SetModEventMask(SA::ModificationFlags::ChangeFold);

	if (0==props.GetInt("undo.redo.lazy")) {
		// Trap for insert/delete notifications (also fired by undo
		// and redo) so that the buttons can be enabled if needed.
		const SA::ModificationFlags flagsCurrent = wEditor.ModEventMask();
		const SA::ModificationFlags flags =
				flagsCurrent |
				SA::ModificationFlags::InsertText |
				SA::ModificationFlags::DeleteText |
				SA::ModificationFlags::LastStepInUndoRedo;
		wEditor.SetModEventMask(flags);

		// LastStepInUndoRedo is probably not needed in the mask; it
		// doesn't seem to fire as an event of its own; just modifies the
		// insert and delete events.
	}

	const SA::UndoSelectionHistoryOption undoSelectionHistory =
		props.GetEnum("undo.selection.history", SA::UndoSelectionHistoryOption::Enabled);
	wEditor.SetUndoSelectionHistory(undoSelectionHistory);
	wOutput.SetUndoSelectionHistory(undoSelectionHistory);

	// Create a margin column for the folding symbols
	wEditor.SetMarginTypeN(2, SA::MarginType::Symbol);

	foldMarginWidth = props.GetInt("fold.margin.width");
	if (foldMarginWidth == 0)
		foldMarginWidth = foldMarginWidthDefault;
	SetFoldWidth();
	wEditor.SetMarginMaskN(2, SA::MaskFolders);
	wEditor.SetMarginSensitiveN(2, true);

	// Define foreground (outline) and background (fill) colour of folds
	const int foldSymbols = props.GetInt("fold.symbols");
	std::string foldFore = props.GetExpandedString("fold.fore");
	if (foldFore.empty()) {
		// Set default colour for outline
		switch (foldSymbols) {
		case 0: // Arrows
			foldFore = "#000000";
			break;
		case 1: // + -
			foldFore = "#FFFFFF";
			break;
		case 2: // Circles
			foldFore = "#404040";
			break;
		case 3: // Squares
			foldFore = "#808080";
			break;
		}
	}
	const SA::ColourAlpha colourFoldFore = ColourAlphaFromString(foldFore);

	std::string foldBack = props.GetExpandedString("fold.back");
	// Set default colour for fill
	if (foldBack.empty()) {
		switch (foldSymbols) {
		case 0:
		case 1:
			foldBack = "#000000";
			break;
		case 2:
		case 3:
			foldBack = "#FFFFFF";
			break;
		}
	}
	const SA::ColourAlpha colourFoldBack = ColourAlphaFromString(foldBack);

	// Enable/disable highlight for current folding block (smallest one that contains the caret)
	const int isHighlightEnabled = props.GetInt("fold.highlight", 0);
	// Define the colour of highlight
	const SA::ColourAlpha colourFoldBlockHighlight = ColourAlphaOfProperty(
		props, "fold.highlight.colour", ColourRGBA(0xFF, 0, 0));

	const int foldStrokeWidth = props.GetInt("fold.stroke.width", 100);
	// Isolated and connected fold markers use foreground and background colours differently
	const MarkerAppearance isolated {
		colourFoldFore, colourFoldBack, colourFoldBlockHighlight, foldStrokeWidth
	};
	const MarkerAppearance connected {
		colourFoldBack, colourFoldFore, colourFoldBlockHighlight, foldStrokeWidth
	};
	switch (foldSymbols) {
	case 0:
		// Arrow pointing right for contracted folders, arrow pointing down for expanded
		DefineMarker(SA::MarkerOutline::FolderOpen, SA::MarkerSymbol::ArrowDown, isolated);
		DefineMarker(SA::MarkerOutline::Folder, SA::MarkerSymbol::Arrow, isolated);
		DefineMarker(SA::MarkerOutline::FolderSub, SA::MarkerSymbol::Empty, isolated);
		DefineMarker(SA::MarkerOutline::FolderTail, SA::MarkerSymbol::Empty, isolated);
		DefineMarker(SA::MarkerOutline::FolderEnd, SA::MarkerSymbol::Empty, isolated);
		DefineMarker(SA::MarkerOutline::FolderOpenMid, SA::MarkerSymbol::Empty, isolated);
		DefineMarker(SA::MarkerOutline::FolderMidTail, SA::MarkerSymbol::Empty, isolated);
		// The highlight is disabled for arrow.
		wEditor.MarkerEnableHighlight(false);
		break;
	case 1:
		// Plus for contracted folders, minus for expanded
		DefineMarker(SA::MarkerOutline::FolderOpen, SA::MarkerSymbol::Minus, isolated);
		DefineMarker(SA::MarkerOutline::Folder, SA::MarkerSymbol::Plus, isolated);
		DefineMarker(SA::MarkerOutline::FolderSub, SA::MarkerSymbol::Empty, isolated);
		DefineMarker(SA::MarkerOutline::FolderTail, SA::MarkerSymbol::Empty, isolated);
		DefineMarker(SA::MarkerOutline::FolderEnd, SA::MarkerSymbol::Empty, isolated);
		DefineMarker(SA::MarkerOutline::FolderOpenMid, SA::MarkerSymbol::Empty, isolated);
		DefineMarker(SA::MarkerOutline::FolderMidTail, SA::MarkerSymbol::Empty, isolated);
		// The highlight is disabled for plus/minus.
		wEditor.MarkerEnableHighlight(false);
		break;
	case 2:
		// Like a flattened tree control using circular headers and curved joins
		DefineMarker(SA::MarkerOutline::FolderOpen, SA::MarkerSymbol::CircleMinus, connected);
		DefineMarker(SA::MarkerOutline::Folder, SA::MarkerSymbol::CirclePlus, connected);
		DefineMarker(SA::MarkerOutline::FolderSub, SA::MarkerSymbol::VLine, connected);
		DefineMarker(SA::MarkerOutline::FolderTail, SA::MarkerSymbol::LCornerCurve, connected);
		DefineMarker(SA::MarkerOutline::FolderEnd, SA::MarkerSymbol::CirclePlusConnected, connected);
		DefineMarker(SA::MarkerOutline::FolderOpenMid, SA::MarkerSymbol::CircleMinusConnected, connected);
		DefineMarker(SA::MarkerOutline::FolderMidTail, SA::MarkerSymbol::TCornerCurve, connected);
		wEditor.MarkerEnableHighlight(isHighlightEnabled);
		break;
	case 3:
		// Like a flattened tree control using square headers
		DefineMarker(SA::MarkerOutline::FolderOpen, SA::MarkerSymbol::BoxMinus, connected);
		DefineMarker(SA::MarkerOutline::Folder, SA::MarkerSymbol::BoxPlus, connected);
		DefineMarker(SA::MarkerOutline::FolderSub, SA::MarkerSymbol::VLine, connected);
		DefineMarker(SA::MarkerOutline::FolderTail, SA::MarkerSymbol::LCorner, connected);
		DefineMarker(SA::MarkerOutline::FolderEnd, SA::MarkerSymbol::BoxPlusConnected, connected);
		DefineMarker(SA::MarkerOutline::FolderOpenMid, SA::MarkerSymbol::BoxMinusConnected, connected);
		DefineMarker(SA::MarkerOutline::FolderMidTail, SA::MarkerSymbol::TCorner, connected);
		wEditor.MarkerEnableHighlight(isHighlightEnabled);
		break;
	}

	wEditor.MarkerSetForeTranslucent(markerBookmark,
			      ColourAlphaOfProperty(props, "bookmark.fore", ColourRGBA(0xbe, 0, 0)));
	wEditor.MarkerSetBackTranslucent(markerBookmark,
			      ColourAlphaOfProperty(props, "bookmark.back", ColourRGBA(0xe2, 0x40, 0x40)));
	wEditor.MarkerSetAlpha(markerBookmark, props.GetEnum("bookmark.alpha", SA::Alpha::NoAlpha));
	wEditor.MarkerSetStrokeWidth(markerBookmark, props.GetInt("bookmark.stroke.width", 100));

	const std::string bookMarkXPM = props.GetString("bookmark.pixmap");
	if (!bookMarkXPM.empty()) {
		wEditor.MarkerDefinePixmap(markerBookmark, bookMarkXPM.c_str());
	} else if (!props.GetString("bookmark.fore").empty()) {
		wEditor.MarkerDefine(markerBookmark, static_cast<SA::MarkerSymbol>(
					     props.GetInt("bookmark.symbol", static_cast<int>(SA::MarkerSymbol::Bookmark))));
	} else {
		// No bookmark.fore setting so display default pixmap.
		wEditor.MarkerDefinePixmap(markerBookmark, reinterpret_cast<const char *>(bookmarkBluegem));
	}

	wEditor.MarkerDefine(markerFilterMatch, SA::MarkerSymbol::Background);
	wEditor.MarkerSetLayer(markerFilterMatch, Scintilla::Layer::UnderText);
	const SA::ColourAlpha filterMatch = ColourAlphaOfProperty(props, "filter.match.back", ColourRGBA(0xff, 0xe0, 0, 0x20));
	wEditor.MarkerSetBackTranslucent(markerFilterMatch, filterMatch);
	wEditor.MarkerSetAlpha(markerFilterMatch, static_cast<SA::Alpha>(filterMatch >> 24));

	wEditor.SetScrollWidth(props.GetInt("horizontal.scroll.width", 2000));
	wEditor.SetScrollWidthTracking(props.GetInt("horizontal.scroll.width.tracking", 1));
	wOutput.SetScrollWidth(props.GetInt("output.horizontal.scroll.width", 2000));
	wOutput.SetScrollWidthTracking(props.GetInt("output.horizontal.scroll.width.tracking", 1));

	// Do these last as they force a style refresh
	wEditor.SetHScrollBar(props.GetInt("horizontal.scrollbar", 1));
	wOutput.SetHScrollBar(props.GetInt("output.horizontal.scrollbar", 1));

	wEditor.SetEndAtLastLine(props.GetInt("end.at.last.line", 1));
	wEditor.SetCaretSticky(props.GetEnum("caret.sticky", SA::CaretSticky::Off));

	// Clear all previous indicators.
	wEditor.SetIndicatorCurrent(indicatorHighlightCurrentWord);
	wEditor.IndicatorClearRange(0, wEditor.Length());
	wOutput.SetIndicatorCurrent(indicatorHighlightCurrentWord);
	wOutput.IndicatorClearRange(0, wOutput.Length());
	currentWordHighlight.statesOfDelay = CurrentWordHighlight::StatesOfDelay::noDelay;

	SetElementColour(SA::Element::FoldLine, "fold.line.colour");
	SetElementColour(SA::Element::HiddenLine, "hidden.line.colour");

	currentWordHighlight.isEnabled = props.GetInt("highlight.current.word", 0) == 1;
	if (currentWordHighlight.isEnabled) {
		const std::string highlightCurrentWordIndicatorString = props.GetExpandedString("highlight.current.word.indicator");
		IndicatorDefinition highlightCurrentWordIndicator(highlightCurrentWordIndicatorString);
		if (highlightCurrentWordIndicatorString.empty()) {
			highlightCurrentWordIndicator.style = SA::IndicatorStyle::RoundBox;
			std::string highlightCurrentWordColourString = props.GetExpandedString("highlight.current.word.colour");
			if (highlightCurrentWordColourString.empty()) {
				// Set default colour for highlight.
				highlightCurrentWordColourString = "#A0A000";
			}
			highlightCurrentWordIndicator.colour = ColourFromString(highlightCurrentWordColourString);
			highlightCurrentWordIndicator.fillAlpha = alphaIndicator;
			highlightCurrentWordIndicator.under = underIndicator;
		}
		const SA::IndicatorNumbers indicatorNumHCWI = static_cast<SA::IndicatorNumbers>(indicatorHighlightCurrentWord);
		SetOneIndicator(wEditor, indicatorNumHCWI,	highlightCurrentWordIndicator);
		SetOneIndicator(wOutput, indicatorNumHCWI, highlightCurrentWordIndicator);
		currentWordHighlight.isOnlyWithSameStyle = props.GetInt("highlight.current.word.by.style", 0) == 1;
		HighlightCurrentWord(true);
	}

	SetIndicatorFromProperty(wEditor, SA::IndicatorNumbers::HistoryRevertedToOriginInsertion, "indicator.reverted.to.origin.insertion");
	SetIndicatorFromProperty(wEditor, SA::IndicatorNumbers::HistoryRevertedToOriginDeletion, "indicator.reverted.to.origin.deletion");
	SetIndicatorFromProperty(wEditor, SA::IndicatorNumbers::HistorySavedInsertion, "indicator.saved.insertion");
	SetIndicatorFromProperty(wEditor, SA::IndicatorNumbers::HistorySavedDeletion, "indicator.saved.deletion");
	SetIndicatorFromProperty(wEditor, SA::IndicatorNumbers::HistoryModifiedInsertion, "indicator.modified.insertion");
	SetIndicatorFromProperty(wEditor, SA::IndicatorNumbers::HistoryModifiedDeletion, "indicator.modified.deletion");
	SetIndicatorFromProperty(wEditor, SA::IndicatorNumbers::HistoryRevertedToModifiedInsertion, "indicator.reverted.to.modified.insertion");
	SetIndicatorFromProperty(wEditor, SA::IndicatorNumbers::HistoryRevertedToModifiedDeletion, "indicator.reverted.to.modified.deletion");

	constexpr size_t markerHistory = static_cast<size_t>(SA::MarkerOutline::HistoryRevertedToOrigin);
	SetMarkerFromProperty(wEditor, markerHistory+0, "marker.reverted.to.origin");
	SetMarkerFromProperty(wEditor, markerHistory+1, "marker.saved");
	SetMarkerFromProperty(wEditor, markerHistory+2, "marker.modified");
	SetMarkerFromProperty(wEditor, markerHistory+3, "marker.reverted.to.modified");

	ReadEditorConfig(fileNameForExtension);

	if (extender) {
		// Check for an extension script
		const int allowLocalScript = propsUser.GetInt("allow.local.script");
		std::string extensionScript;
		if (allowLocalScript) {
			extensionScript = props.GetNewExpandString("extension.", fileNameForExtension);
		} else {
			extensionScript = propsUser.GetNewExpandString("extension.", fileNameForExtension);
		}
		const GUI::gui_string extensionFile = GUI::StringFromUTF8(extensionScript);
		if (!extensionFile.empty()) {
			std::optional<FilePath> scriptPath;
			if (allowLocalScript) {
				// Find file in document directory
				scriptPath = FindPath(extensionFile, filePath.Directory());
			}
			if (!scriptPath) {
				// Find file in global directory
				scriptPath = FindPath(extensionFile, GetDefaultDirectory());
				if (!scriptPath) {
					// Find as completely specified file name
					scriptPath = FindPath(extensionFile, {});
				}
			}
			if (scriptPath) {
				extender->Load(scriptPath->AsUTF8().c_str());
			}
		}
	}

	delayBeforeAutoSave = props.GetInt("save.on.timer");
	if (delayBeforeAutoSave) {
		TimerStart(timerAutoSave);
	} else {
		TimerEnd(timerAutoSave);
	}

	firstPropertiesRead = false;
	needReadProperties = false;
}

void SciTEBase::ReadEditorConfig(const std::string &fileNameForExtension) {
	std::map<std::string, std::string> eConfig = editorConfig->MapFromAbsolutePath(filePath);
	for (const std::pair<const std::string, std::string> &pss : eConfig) {
		try {
			if (pss.first == "indent_style") {
				const bool useTabs = pss.second == "tab";
				propsDiscovered.Set("use.tabs", useTabs ? "1" : "0");
				propsDiscovered.Set("use.tabs." + fileNameForExtension, useTabs ? "1" : "0");
				wEditor.SetUseTabs(useTabs);
			} else if (pss.first == "indent_size") {
				std::string sIndentSize = pss.second;
				if (sIndentSize == "tab") {
					sIndentSize = props.GetExpandedString("tabsize");
					if (sIndentSize.empty()) {
						sIndentSize = "8";
					}
				}
				propsDiscovered.Set("indent.size", sIndentSize);
				propsDiscovered.Set("indent.size." + fileNameForExtension, sIndentSize);
				wEditor.SetIndent(std::stoi(sIndentSize));
			} else if (pss.first == "tab_width") {
				propsDiscovered.Set("tabsize", pss.second);
				propsDiscovered.Set("tab.size." + fileNameForExtension, pss.second);
				wEditor.SetTabWidth(std::stoi(pss.second));
			} else if (pss.first == "end_of_line") {
				if (pss.second == "lf") {
					propsDiscovered.Set("eol.mode", "LF");
					wEditor.SetEOLMode(SA::EndOfLine::Lf);
				} else if (pss.second == "cr") {
					propsDiscovered.Set("eol.mode", "CR");
					wEditor.SetEOLMode(SA::EndOfLine::Cr);
				} else if (pss.second == "crlf") {
					propsDiscovered.Set("eol.mode", "CRLF");
					wEditor.SetEOLMode(SA::EndOfLine::CrLf);
				}
			} else if (pss.first == "charset") {
				if (pss.second == "latin1") {
					CurrentBuffer()->unicodeMode = UniMode::uni8Bit;
					codePage = 0;
				} else {
					if (pss.second == "utf-8")
						CurrentBuffer()->unicodeMode = UniMode::cookie;
					if (pss.second == "utf-8-bom")
						CurrentBuffer()->unicodeMode = UniMode::utf8;
					if (pss.second == "utf-16be")
						CurrentBuffer()->unicodeMode = UniMode::uni16BE;
					if (pss.second == "utf-16le")
						CurrentBuffer()->unicodeMode = UniMode::uni16LE;
					codePage = SA::CpUtf8;
				}
				wEditor.SetCodePage(codePage);
			} else if (pss.first == "trim_trailing_whitespace") {
				stripTrailingSpaces = pss.second == "true";
			} else if (pss.first == "insert_final_newline") {
				ensureFinalLineEnd = pss.second == "true";
			}
		} catch (std::invalid_argument &) {
			std::string diagnostic = "Invalid argument in .editorconfig '";
			diagnostic += pss.first;
			diagnostic += "=";
			diagnostic += pss.second;
			diagnostic += "'.\n";
			OutputAppendString(diagnostic);
			SetOutputVisibility(true);
		}
	}
}

namespace {

// Scintillua's style numbers are not constant, so ask it for names of styles and create a
// mapping of style numbers to more constant style definitions.
// For example, if Scintillua reports for the cpp lexer that style number 2 is a 'comment',
// create the property:
//   style.scintillua.cpp.2=$(scintillua.styles.comment)
// That way the user can define 'scintillua.styles.comment' once and it will be used for whatever
// the style number for comments is in any given lexer.
// Similarly, if Scintillua reports for the lua lexer that style number 20 is 'string.longstring',
// create the property:
//   style.scintillua.lua.20=$(scintillua.styles.string),$(scintillua.styles.string.longstring)
void SetScintilluaStyles(GUI::ScintillaWindow &wEditor, PropSetFile& props, const char *languageName) {
	const auto setStyle = [&wEditor, &props, &languageName](int style) {
		std::string finalPropStr;
		const std::string &name = wEditor.NameOfStyle(style);
		size_t end = std::string::npos;
		do {
			end = name.find('.', ++end);
			char propStr[128] = "";
			snprintf(propStr, std::size(propStr), "$(scintillua.styles.%s),", end == std::string::npos ?
				name.c_str() : name.substr(0, end).c_str());
			finalPropStr += propStr;
		} while (end != std::string::npos);
		const std::string key = StyleName(languageName, style);
		props.Set(key, finalPropStr);
	};
	const int namedStyles = wEditor.NamedStyles(); // this count includes predefined styles
	constexpr int LastPredefined = static_cast<int>(Scintilla::StylesCommon::LastPredefined);
	constexpr int numPredefined = LastPredefined - StyleDefault + 1;
	for (int i = 0; i < std::min(namedStyles - numPredefined, StyleDefault); i++) {
		setStyle(i);
	}
	for (int i = StyleDefault; i <= LastPredefined; i++) {
		setStyle(i);
	}
	for (int i = LastPredefined + 1; i < namedStyles; i++) {
		setStyle(i);
	}
}

}

void SciTEBase::ReadFontProperties() {
	const std::string monospaceFonts = props.GetExpandedString("font.monospaced.list");
	monospacedList = StringSplit(monospaceFonts, ';');

	if (language.starts_with("scintillua.") && language.length() < 240) {
		SetScintilluaStyles(wEditor, props, language.c_str());
	}

	// Set styles
	// For each window set the global default style, then the language default style, then the other global styles, then the other language styles

	const SA::FontQuality fontQuality = props.GetEnum("font.quality", SA::FontQuality::QualityDefault);
	wEditor.SetFontQuality(fontQuality);
	wOutput.SetFontQuality(fontQuality);

	wEditor.StyleResetDefault();
	wOutput.StyleResetDefault();

	std::string fontLocale = props.GetExpandedString("font.locale");
	if (!fontLocale.empty()) {
		wEditor.SetFontLocale(fontLocale.c_str());
		wOutput.SetFontLocale(fontLocale.c_str());
	}

	std::string sval = props.GetExpandedString(StyleName("*", StyleDefault));
	SetOneStyle(wEditor, StyleDefault, sval);
	SetOneStyle(wOutput, StyleDefault, sval);

	sval = props.GetExpandedString(StyleName(language, StyleDefault));
	SetOneStyle(wEditor, StyleDefault, sval);

	wEditor.StyleClearAll();

	SetStyleFor(wEditor, "*");
	SetStyleFor(wEditor, language.c_str());
	if (props.GetInt("error.inline")) {
		wEditor.ReleaseAllExtendedStyles();
		diagnosticStyleStart = wEditor.AllocateExtendedStyles(diagnosticStyles);
		SetStyleBlock(wEditor, "error", diagnosticStyleStart, diagnosticStyleStart+diagnosticStyles-1);
	}

	const int diffToSecondary = wEditor.DistanceToSecondaryStyles();
	for (const unsigned char subStyleBase : subStyleBases) {
		const int subStylesStart = wEditor.SubStylesStart(subStyleBase);
		const int subStylesLength = wEditor.SubStylesLength(subStyleBase);
		for (int subStyle=0; subStyle<subStylesLength; subStyle++) {
			for (int active=0; active<(diffToSecondary?2:1); active++) {
				const int activity = active * diffToSecondary;
				sval = props.GetExpandedString(
					StyleName(language, subStyleBase + activity, subStyle + 1));
				SetOneStyle(wEditor, subStylesStart + subStyle + activity, sval);
			}
		}
	}

	// Turn grey while loading
	if (CurrentBuffer()->lifeState == Buffer::LifeState::reading)
		wEditor.StyleSetBack(StyleDefault, 0xEEEEEE);

	wOutput.StyleClearAll();

	sval = props.GetExpandedString(StyleName("errorlist", StyleDefault));
	SetOneStyle(wOutput, StyleDefault, sval);

	wOutput.StyleClearAll();

	SetStyleFor(wOutput, "*");
	SetStyleFor(wOutput, "errorlist");

	if (CurrentBuffer()->useMonoFont) {
		sval = props.GetExpandedString("font.monospace");
		StyleDefinition sd(sval);
		for (int style = 0; style <= StyleMax; style++) {
			if (style != static_cast<int>(SA::StylesCommon::LineNumber)) {
				if (sd.specified & StyleDefinition::sdFont) {
					wEditor.StyleSetFont(style, sd.font.c_str());
				}
				if (sd.specified & StyleDefinition::sdSize) {
					wEditor.StyleSetSizeFractional(style, sd.FractionalSize());
				}
			}
		}
	}
}

// Properties that are interactively modifiable are only read from the properties file once.
void SciTEBase::SetPropertiesInitial() {
	splitVertical = props.GetInt("split.vertical");
	openFilesHere = props.GetInt("check.if.already.open");
	wrap = props.GetInt("wrap");
	wrapOutput = props.GetInt("output.wrap");
	indentationWSVisible = props.GetInt("view.indentation.whitespace", 1);
	sbVisible = props.GetInt("statusbar.visible");
	tbVisible = props.GetInt("toolbar.visible");
	tabVisible = props.GetInt("tabbar.visible");
	tabMultiLine = props.GetInt("tabbar.multiline");
	lineNumbers = props.GetInt("line.margin.visible");
	margin = props.GetInt("margin.width");
	foldMargin = props.GetInt("fold.margin.width", foldMarginWidthDefault);

	matchCase = props.GetInt("find.replace.matchcase");
	regExp = props.GetInt("find.replace.regexp");
	unSlash = props.GetInt("find.replace.escapes");
	wrapFind = props.GetInt("find.replace.wrap", 1);
	focusOnReplace = props.GetInt("find.replacewith.focus", 1);
}

GUI::gui_string Localization::Text(std::string_view sv, bool retainIfNotFound) const {
	const std::string sEllipse("...");	// An ASCII ellipse
	const std::string utfEllipse("\xe2\x80\xa6");	// A UTF-8 ellipse
	std::string translation(sv);
	const int ellipseIndicator = Remove(translation, sEllipse);
	const int utfEllipseIndicator = Remove(translation, utfEllipse);
	const std::string menuAccessIndicatorChar(1, static_cast<char>(menuAccessIndicator[0]));
	const int accessKeyPresent = Remove(translation, menuAccessIndicatorChar);
	LowerCaseAZ(translation);
	Substitute(translation, "\n", "\\n");
	translation = GetString(translation);
	if (!translation.empty()) {
		if (ellipseIndicator)
			translation += sEllipse;
		if (utfEllipseIndicator)
			translation += utfEllipse;
		if (0 == accessKeyPresent) {
#if !defined(GTK)
			// Following codes are required because accelerator is not always
			// part of alphabetical word in several language. In these cases,
			// accelerator is written like "(&O)".
			const size_t posOpenParenAnd = translation.find("(&");
			if ((posOpenParenAnd != std::string::npos) && (translation.find(")", posOpenParenAnd) == posOpenParenAnd + 3)) {
				translation.erase(posOpenParenAnd, 4);
			} else {
				Remove(translation, std::string("&"));
			}
#else
			Remove(translation, std::string("&"));
#endif
		}
		Substitute(translation, "&", menuAccessIndicatorChar);
		Substitute(translation, "\\n", "\n");
	} else {
		translation = missing;
	}
	if ((!translation.empty()) || !retainIfNotFound) {
		return GUI::StringFromUTF8(translation);
	}
	return GUI::StringFromUTF8(std::string(sv));
}

GUI::gui_string SciTEBase::LocaliseMessage(std::string_view s, std::optional<GUI::gui_string_view> param0, std::optional<GUI::gui_string_view> param1, std::optional<GUI::gui_string_view> param2) {
	GUI::gui_string translation = localiser.Text(s);
	if (param0) {
		Substitute(translation, GUI_TEXT("^0"), *param0);
	}
	if (param1) {
		Substitute(translation, GUI_TEXT("^1"), *param1);
	}
	if (param2) {
		Substitute(translation, GUI_TEXT("^2"), *param2);
	}
	return translation;
}

void SciTEBase::ReadLocalization() {
	localiser.Clear();
	GUI::gui_string title = GUI_TEXT("locale.properties");
	const std::string localeProps = props.GetExpandedString("locale.properties");
	if (!localeProps.empty()) {
		title = GUI::StringFromUTF8(localeProps);
	}
	FilePath propdir = GetSciteDefaultHome();
	FilePath localePath(propdir, title);
	localiser.Read(localePath, propdir, filter, &importFiles, 0);
	localiser.SetMissing(props.GetString("translation.missing"));
	localiser.read = true;
}

void SciTEBase::ReadPropertiesInitial() {
	SetPropertiesInitial();
	const int sizeHorizontal = props.GetInt("output.horizontal.size", 0);
	const int sizeVertical = props.GetInt("output.vertical.size", 0);
	const int hideOutput = props.GetInt("output.initial.hide", 0);
	if ((!splitVertical && (sizeVertical > 0) && (heightOutput < sizeVertical)) ||
			(splitVertical && (sizeHorizontal > 0) && (heightOutput < sizeHorizontal))) {
		previousHeightOutput = splitVertical ? sizeHorizontal : sizeVertical;
		if (!hideOutput) {
			heightOutput = NormaliseSplit(previousHeightOutput);
			SizeSubWindows();
			Redraw();
		}
	}
	ViewWhitespace(props.GetInt("view.whitespace"));
	wEditor.SetIndentationGuides(props.GetInt("view.indentation.guides") ?
				     indentExamine : SA::IndentView::None);

	wEditor.SetViewEOL(props.GetInt("view.eol"));
	wEditor.SetZoom(props.GetInt("magnification"));
	wOutput.SetZoom(props.GetInt("output.magnification"));
	wEditor.SetWrapMode(wrap ? wrapStyle : SA::Wrap::None);
	wOutput.SetWrapMode(wrapOutput ? wrapStyle : SA::Wrap::None);

	std::string menuLanguageProp = props.GetExpandedString("menu.language");
	std::ranges::replace(menuLanguageProp, '|', '\0');
	const char *sMenuLanguage = menuLanguageProp.c_str();
	while (*sMenuLanguage) {
		LanguageMenuItem lmi;
		lmi.menuItem = sMenuLanguage;
		sMenuLanguage += strlen(sMenuLanguage) + 1;
		lmi.extension = sMenuLanguage;
		sMenuLanguage += strlen(sMenuLanguage) + 1;
		lmi.menuKey = sMenuLanguage;
		sMenuLanguage += strlen(sMenuLanguage) + 1;
		languageMenu.push_back(lmi);
	}
	SetLanguageMenu();

	// load the user defined short cut props
	std::string shortCutProp = props.GetNewExpandString("user.shortcuts");
	if (!shortCutProp.empty()) {
		const size_t pipes = std::ranges::count(shortCutProp, '|');
		std::ranges::replace(shortCutProp, '|', '\0');
		const char *sShortCutProp = shortCutProp.c_str();
		for (size_t item = 0; item < pipes/2; item++) {
			ShortcutItem sci;
			sci.menuKey = sShortCutProp;
			sShortCutProp += strlen(sShortCutProp) + 1;
			sci.menuCommand = sShortCutProp;
			sShortCutProp += strlen(sShortCutProp) + 1;
			shortCutItemList.push_back(sci);
		}
	}
	// end load the user defined short cut props

	props.SetPath("SciteDefaultHome", GetSciteDefaultHome());
	props.SetPath("SciteUserHome", GetSciteUserHome());
}

FilePath SciTEBase::GetDefaultPropertiesFileName() {
	return FilePath(GetSciteDefaultHome(), propGlobalFileName);
}

FilePath SciTEBase::GetAbbrevPropertiesFileName() {
	return FilePath(GetSciteUserHome(), propAbbrevFileName);
}

FilePath SciTEBase::GetUserPropertiesFileName() {
	return FilePath(GetSciteUserHome(), propUserFileName);
}

FilePath SciTEBase::GetLocalPropertiesFileName() {
	return FilePath(filePath.Directory(), propLocalFileName);
}

FilePath SciTEBase::GetDirectoryPropertiesFileName() {
	FilePath propfile;

	if (filePath.IsSet()) {
		propfile.Set(filePath.Directory(), propDirectoryFileName);

		// if this file does not exist try to find the prop file in a parent directory
		while (!propfile.Directory().IsRoot() && !propfile.Exists()) {
			propfile.Set(propfile.Directory().Directory(), propDirectoryFileName);
		}

		// not found -> set it to the initial directory
		if (!propfile.Exists()) {
			propfile.Set(filePath.Directory(), propDirectoryFileName);
		}
	}
	return propfile;
}

void SciTEBase::OpenProperties(int propsFile) {
	FilePath propfile;
	switch (propsFile) {
	case IDM_OPENLOCALPROPERTIES:
		propfile = GetLocalPropertiesFileName();
		Open(propfile, ofQuiet);
		break;
	case IDM_OPENUSERPROPERTIES:
		propfile = GetUserPropertiesFileName();
		Open(propfile, ofQuiet);
		break;
	case IDM_OPENABBREVPROPERTIES:
		propfile = pathAbbreviations;
		Open(propfile, ofQuiet);
		break;
	case IDM_OPENGLOBALPROPERTIES:
		propfile = GetDefaultPropertiesFileName();
		Open(propfile, ofQuiet);
		break;
	case IDM_OPENLUAEXTERNALFILE: {
			GUI::gui_string extlua = GUI::StringFromUTF8(props.GetExpandedString("ext.lua.startup.script"));
			if (!extlua.empty()) {
				Open(extlua, ofQuiet);
			}
			break;
		}
	case IDM_OPENDIRECTORYPROPERTIES: {
			propfile = GetDirectoryPropertiesFileName();
			const bool alreadyExists = propfile.Exists();
			Open(propfile, ofQuiet);
			if (!alreadyExists)
				SaveAsDialog();
		}
		break;
	}
}

// return the int value of the command name passed in.
int SciTEBase::GetMenuCommandAsInt(const std::string &commandName) {
	int i = IFaceTable::FindConstant(commandName.c_str());
	if (i != -1) {
		return IFaceTable::constants[i].value;
	}

	// Check also for a SCI command, as long as it has no parameters
	i = IFaceTable::FindFunctionByConstantName(commandName.c_str());
	if (i != -1 &&
			IFaceTable::functions[i].paramType[0] == iface_void &&
			IFaceTable::functions[i].paramType[1] == iface_void) {
		return IFaceTable::functions[i].value;
	}

	// Otherwise we might have entered a number as command to access a "SCI_" command
	return IntegerFromString(commandName, 0);
}
