// This file is part of Notepad++ project
// Copyright (C)2021 Don HO <don.h@free.fr>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.


#include <shlwapi.h>
#include "FindReplaceDlg.h"
#include "ScintillaEditView.h"
#include "Notepad_plus_msgs.h"
#include "localization.h"
#include "Common.h"
#include "Utf8.h"

using namespace std;

FindOption * FindReplaceDlg::_env;
FindOption FindReplaceDlg::_options;

#define SHIFTED 0x8000

void addText2Combo(const TCHAR * txt2add, HWND hCombo)
{
	if (!hCombo) return;
	if (!lstrcmp(txt2add, TEXT(""))) return;

	auto i = ::SendMessage(hCombo, CB_FINDSTRINGEXACT, static_cast<WPARAM>(-1), reinterpret_cast<LPARAM>(txt2add));
	if (i != CB_ERR) // found
	{
		::SendMessage(hCombo, CB_DELETESTRING, i, 0);
	}

	i = ::SendMessage(hCombo, CB_INSERTSTRING, 0, reinterpret_cast<LPARAM>(txt2add));
	::SendMessage(hCombo, CB_SETCURSEL, i, 0);
}

generic_string getTextFromCombo(HWND hCombo)
{
	TCHAR str[FINDREPLACE_MAXLENGTH] = { '\0' };
	::SendMessage(hCombo, WM_GETTEXT, FINDREPLACE_MAXLENGTH - 1, reinterpret_cast<LPARAM>(str));
	return generic_string(str);
}

void delLeftWordInEdit(HWND hEdit)
{
	TCHAR str[FINDREPLACE_MAXLENGTH] = { '\0' };
	::SendMessage(hEdit, WM_GETTEXT, FINDREPLACE_MAXLENGTH - 1, reinterpret_cast<LPARAM>(str));
	WORD cursor = 0;
	::SendMessage(hEdit, EM_GETSEL, (WPARAM)&cursor, 0);
	WORD wordstart = cursor;
	while (wordstart > 0) {
		TCHAR c = str[wordstart - 1];
		if (c != ' ' && c != '\t')
			break;
		--wordstart;
	}
	while (wordstart > 0) {
		TCHAR c = str[wordstart - 1];
		if (c == ' ' || c == '\t')
			break;
		--wordstart;
	}
	if (wordstart < cursor) {
		::SendMessage(hEdit, EM_SETSEL, (WPARAM)wordstart, (LPARAM)cursor);
		::SendMessage(hEdit, EM_REPLACESEL, (WPARAM)TRUE, reinterpret_cast<LPARAM>(L""));
	}
}

LRESULT run_swapButtonProc(WNDPROC oldEditProc, HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
		case WM_RBUTTONUP:
		{
			::SendMessage(GetParent(hwnd), message, wParam, lParam);
			break;
		}

		default:
			break;
	}
	return ::CallWindowProc(oldEditProc, hwnd, message, wParam, lParam);
}

int Searching::convertExtendedToString(const TCHAR * query, TCHAR * result, int length)
{	//query may equal to result, since it always gets smaller
	int i = 0, j = 0;
	int charLeft = length;
	TCHAR current;
	while (i < length)
	{	//because the backslash escape quences always reduce the size of the generic_string, no overflow checks have to be made for target, assuming parameters are correct
		current = query[i];
		--charLeft;
		if (current == '\\' && charLeft)
		{	//possible escape sequence
			++i;
			--charLeft;
			current = query[i];
			switch(current)
			{
				case 'r':
					result[j] = '\r';
					break;
				case 'n':
					result[j] = '\n';
					break;
				case '0':
					result[j] = '\0';
					break;
				case 't':
					result[j] = '\t';
					break;
				case '\\':
					result[j] = '\\';
					break;
				case 'b':
				case 'd':
				case 'o':
				case 'x':
				case 'u':
				{
					int size = 0, base = 0;
					if (current == 'b')
					{	//11111111
						size = 8, base = 2;
					}
					else if (current == 'o')
					{	//377
						size = 3, base = 8;
					}
					else if (current == 'd')
					{	//255
						size = 3, base = 10;
					}
					else if (current == 'x')
					{	//0xFF
						size = 2, base = 16;
					}
					else if (current == 'u')
					{	//0xCDCD
						size = 4, base = 16;
					}

					if (charLeft >= size)
					{
						int res = 0;
						if (Searching::readBase(query+(i+1), &res, base, size))
						{
							result[j] = static_cast<TCHAR>(res);
							i += size;
							break;
						}
					}
					//not enough chars to make parameter, use default method as fallback
					[[fallthrough]];
				}

				default:
				{	//unknown sequence, treat as regular text
					result[j] = '\\';
					++j;
					result[j] = current;
					break;
				}
			}
		}
		else
		{
			result[j] = query[i];
		}
		++i;
		++j;
	}
	result[j] = 0;
	return j;
}

bool Searching::readBase(const TCHAR * str, int * value, int base, int size)
{
	int i = 0, temp = 0;
	*value = 0;
	TCHAR max = '0' + static_cast<TCHAR>(base) - 1;
	TCHAR current;
	while (i < size)
	{
		current = str[i];
		if (current >= 'A')
		{
			current &= 0xdf;
			current -= ('A' - '0' - 10);
		}
		else if (current > '9')
			return false;

		if (current >= '0' && current <= max)
		{
			temp *= base;
			temp += (current - '0');
		}
		else
		{
			return false;
		}
		++i;
	}
	*value = temp;
	return true;
}

void Searching::displaySectionCentered(size_t posStart, size_t posEnd, ScintillaEditView * pEditView, bool isDownwards)
{
	// Make sure target lines are unfolded
	pEditView->execute(SCI_ENSUREVISIBLE, pEditView->execute(SCI_LINEFROMPOSITION, posStart));
	pEditView->execute(SCI_ENSUREVISIBLE, pEditView->execute(SCI_LINEFROMPOSITION, posEnd));

	// Jump-scroll to center, if current position is out of view
	pEditView->execute(SCI_SETVISIBLEPOLICY, CARET_JUMPS | CARET_EVEN);
	pEditView->execute(SCI_ENSUREVISIBLEENFORCEPOLICY, pEditView->execute(SCI_LINEFROMPOSITION, isDownwards ? posEnd : posStart));
	// When searching up, the beginning of the (possible multiline) result is important, when scrolling down the end
	pEditView->execute(SCI_GOTOPOS, isDownwards ? posEnd : posStart);
	pEditView->execute(SCI_SETVISIBLEPOLICY, CARET_EVEN);
	pEditView->execute(SCI_ENSUREVISIBLEENFORCEPOLICY, pEditView->execute(SCI_LINEFROMPOSITION, isDownwards ? posEnd : posStart));

	// Adjust so that we see the entire match; primarily horizontally
	pEditView->execute(SCI_SCROLLRANGE, posStart, posEnd);

	// Move cursor to end of result and select result
	pEditView->execute(SCI_GOTOPOS, posEnd);
	pEditView->execute(SCI_SETANCHOR, posStart);

	// Update Scintilla's knowledge about what column the caret is in, so that if user
	// does up/down arrow as first navigation after the search result is selected,
	// the caret doesn't jump to an unexpected column
	pEditView->execute(SCI_CHOOSECARETX);
}

WNDPROC FindReplaceDlg::originalFinderProc = nullptr;
WNDPROC FindReplaceDlg::originalComboEditProc = nullptr;

// important : to activate all styles
const int STYLING_MASK = 255;

FindReplaceDlg::~FindReplaceDlg()
{
	_tab.destroy();
	delete _pFinder;
	for (int n = static_cast<int32_t>(_findersOfFinder.size()) - 1; n >= 0; n--)
	{
		delete _findersOfFinder[n];
		_findersOfFinder.erase(_findersOfFinder.begin() + n);
	}

	if (_shiftTrickUpTip)
		::DestroyWindow(_shiftTrickUpTip);

	if (_2ButtonsTip)
		::DestroyWindow(_2ButtonsTip);

	if (_filterTip)
		::DestroyWindow(_filterTip);

	if (_hMonospaceFont)
		::DeleteObject(_hMonospaceFont);

	if (_hLargerBolderFont)
		::DeleteObject(_hLargerBolderFont);

	if (_hCourrierNewFont)
		::DeleteObject(_hCourrierNewFont);

	delete[] _uniFileName;
}

void FindReplaceDlg::create(int dialogID, bool isRTL, bool msgDestParent, bool toShow)
{
	StaticDialog::create(dialogID, isRTL, msgDestParent);
	fillFindHistory();
	_currentStatus = REPLACE_DLG;
	initOptionsFromDlg();

	_statusBar.init(GetModuleHandle(NULL), _hSelf, 0);
	_statusBar.display();

	DPIManager& dpiManager = NppParameters::getInstance()._dpiManager;

	RECT rect{};
	getClientRect(rect);
	_tab.init(_hInst, _hSelf, false, true);
	NppDarkMode::subclassTabControl(_tab.getHSelf());
	int tabDpiDynamicalHeight = dpiManager.scaleY(13);
	_tab.setFont(TEXT("Tahoma"), tabDpiDynamicalHeight);

	const TCHAR *find = TEXT("Find");
	const TCHAR *replace = TEXT("Replace");
	const TCHAR *findInFiles = TEXT("Find in Files");
	const TCHAR *findInProjects = TEXT("Find in Projects");
	const TCHAR *mark = TEXT("Mark");

	_tab.insertAtEnd(find);
	_tab.insertAtEnd(replace);
	_tab.insertAtEnd(findInFiles);
	_tab.insertAtEnd(findInProjects);
	_tab.insertAtEnd(mark);

	_tab.reSizeTo(rect);
	_tab.display();

	_initialClientWidth = rect.right - rect.left;

	//fill min dialog size info
	getWindowRect(_initialWindowRect);
	_initialWindowRect.right = _initialWindowRect.right - _initialWindowRect.left + dpiManager.scaleX(10);
	_initialWindowRect.left = 0;
	_initialWindowRect.bottom = _initialWindowRect.bottom - _initialWindowRect.top;
	_initialWindowRect.top = 0;

	RECT dlgRc{};
	getWindowRect(dlgRc);

	RECT countRc{};
	::GetWindowRect(::GetDlgItem(_hSelf, IDCCOUNTALL), &countRc);

	NppParameters& nppParam = NppParameters::getInstance();
	NppGUI& nppGUI = nppParam.getNppGUI();

	const UINT swpFlags = toShow ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
	if (nppGUI._findWindowPos.bottom - nppGUI._findWindowPos.top != 0)  // check height against 0 as a test of valid data from config
	{
		RECT rc = getViewablePositionRect(nppGUI._findWindowPos);
		::SetWindowPos(_hSelf, HWND_TOP, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, swpFlags);
	}
	else
	{
		goToCenter(swpFlags);
	}

	RECT rcStatusBar{};
	::GetClientRect(_statusBar.getHSelf(), &rcStatusBar);
	_lesssModeHeight = (countRc.bottom - dlgRc.top) + (rcStatusBar.bottom - rcStatusBar.top) + dpiManager.scaleY(10);

	if (nppGUI._findWindowLessMode)
	{
		// reverse the value of _findWindowLessMode because the value will be inversed again in IDD_RESIZE_TOGGLE_BUTTON
		nppGUI._findWindowLessMode = false;

		::SendMessage(_hSelf, WM_COMMAND, IDD_RESIZE_TOGGLE_BUTTON, 0);
	}
}

void FindReplaceDlg::fillFindHistory()
{
	NppParameters& nppParams = NppParameters::getInstance();
	FindHistory & findHistory = nppParams.getFindHistory();

	fillComboHistory(IDFINDWHAT, findHistory._findHistoryFinds);
	fillComboHistory(IDREPLACEWITH, findHistory._findHistoryReplaces);
	fillComboHistory(IDD_FINDINFILES_FILTERS_COMBO, findHistory._findHistoryFilters);
	fillComboHistory(IDD_FINDINFILES_DIR_COMBO, findHistory._findHistoryPaths);

	::SendDlgItemMessage(_hSelf, IDWRAP, BM_SETCHECK, findHistory._isWrap, 0);
	::SendDlgItemMessage(_hSelf, IDWHOLEWORD, BM_SETCHECK, findHistory._isMatchWord, 0);
	::SendDlgItemMessage(_hSelf, IDMATCHCASE, BM_SETCHECK, findHistory._isMatchCase, 0);
	::SendDlgItemMessage(_hSelf, IDC_BACKWARDDIRECTION, BM_SETCHECK, !findHistory._isDirectionDown, 0);

	::SendDlgItemMessage(_hSelf, IDD_FINDINFILES_INHIDDENDIR_CHECK, BM_SETCHECK, findHistory._isFifInHiddenFolder, 0);
	::SendDlgItemMessage(_hSelf, IDD_FINDINFILES_RECURSIVE_CHECK, BM_SETCHECK, findHistory._isFifRecuisive, 0);
	::SendDlgItemMessage(_hSelf, IDD_FINDINFILES_FOLDERFOLLOWSDOC_CHECK, BM_SETCHECK, findHistory._isFolderFollowDoc, 0);

	::SendDlgItemMessage(_hSelf, IDD_FINDINFILES_PROJECT1_CHECK, BM_SETCHECK, findHistory._isFifProjectPanel_1, 0);
	::SendDlgItemMessage(_hSelf, IDD_FINDINFILES_PROJECT2_CHECK, BM_SETCHECK, findHistory._isFifProjectPanel_2, 0);
	::SendDlgItemMessage(_hSelf, IDD_FINDINFILES_PROJECT3_CHECK, BM_SETCHECK, findHistory._isFifProjectPanel_3, 0);

	::SendDlgItemMessage(_hSelf, IDNORMAL, BM_SETCHECK, findHistory._searchMode == FindHistory::normal, 0);
	::SendDlgItemMessage(_hSelf, IDEXTENDED, BM_SETCHECK, findHistory._searchMode == FindHistory::extended, 0);
	::SendDlgItemMessage(_hSelf, IDREGEXP, BM_SETCHECK, findHistory._searchMode == FindHistory::regExpr, 0);
	::SendDlgItemMessage(_hSelf, IDREDOTMATCHNL, BM_SETCHECK, findHistory._dotMatchesNewline, 0);

	::SendDlgItemMessage(_hSelf, IDC_MARKLINE_CHECK, BM_SETCHECK, findHistory._isBookmarkLine, 0);
	::SendDlgItemMessage(_hSelf, IDC_PURGE_CHECK, BM_SETCHECK, findHistory._isPurge, 0);

	::SendDlgItemMessage(_hSelf, IDC_2_BUTTONS_MODE, BM_SETCHECK, findHistory._isSearch2ButtonsMode, 0);

	if (findHistory._searchMode == FindHistory::regExpr)
	{
		//regex doesn't allow wholeword
		::SendDlgItemMessage(_hSelf, IDWHOLEWORD, BM_SETCHECK, BST_UNCHECKED, 0);
		enableFindDlgItem(IDWHOLEWORD, false);

		// regex upward search is disabled
		::SendDlgItemMessage(_hSelf, IDC_BACKWARDDIRECTION, BM_SETCHECK, BST_UNCHECKED, 0);
		enableFindDlgItem(IDC_BACKWARDDIRECTION, nppParams.regexBackward4PowerUser());
		enableFindDlgItem(IDC_FINDPREV, nppParams.regexBackward4PowerUser());

		// If the search mode from history is regExp then enable the checkbox (. matches newline)
		enableFindDlgItem(IDREDOTMATCHNL);
	}

	if (nppParams.isTransparentAvailable())
	{
		showFindDlgItem(IDC_TRANSPARENT_CHECK);
		showFindDlgItem(IDC_TRANSPARENT_GRPBOX);
		showFindDlgItem(IDC_TRANSPARENT_LOSSFOCUS_RADIO);
		showFindDlgItem(IDC_TRANSPARENT_ALWAYS_RADIO);
		showFindDlgItem(IDC_PERCENTAGE_SLIDER);

		::SendDlgItemMessage(_hSelf, IDC_PERCENTAGE_SLIDER, TBM_SETRANGE, FALSE, MAKELONG(20, 200));
		::SendDlgItemMessage(_hSelf, IDC_PERCENTAGE_SLIDER, TBM_SETPOS, TRUE, findHistory._transparency);

		if (findHistory._transparencyMode == FindHistory::none)
		{
			enableFindDlgItem(IDC_TRANSPARENT_LOSSFOCUS_RADIO, false);
			enableFindDlgItem(IDC_TRANSPARENT_ALWAYS_RADIO, false);
			enableFindDlgItem(IDC_PERCENTAGE_SLIDER, false);
		}
		else
		{
			::SendDlgItemMessage(_hSelf, IDC_TRANSPARENT_CHECK, BM_SETCHECK, TRUE, 0);

			int id;
			if (findHistory._transparencyMode == FindHistory::onLossingFocus)
			{
				id = IDC_TRANSPARENT_LOSSFOCUS_RADIO;
			}
			else
			{
				id = IDC_TRANSPARENT_ALWAYS_RADIO;
				(NppParameters::getInstance()).SetTransparent(_hSelf, findHistory._transparency);

			}
			::SendDlgItemMessage(_hSelf, id, BM_SETCHECK, TRUE, 0);
		}
	}
}

void FindReplaceDlg::fillComboHistory(int id, const vector<generic_string> & strings)
{
	HWND hCombo = ::GetDlgItem(_hSelf, id);

	for (vector<generic_string>::const_reverse_iterator i = strings.rbegin() ; i != strings.rend(); ++i)
	{
		addText2Combo(i->c_str(), hCombo);
	}

	//empty string is not added to CB items, so we need to set it manually
	if (!strings.empty() && strings.begin()->empty())
	{
		SetWindowText(hCombo, _T(""));
		return;
	}

	::SendMessage(hCombo, CB_SETCURSEL, 0, 0); // select first item
}


void FindReplaceDlg::saveFindHistory()
{
	if (! isCreated()) return;
	FindHistory& findHistory = (NppParameters::getInstance()).getFindHistory();

	saveComboHistory(IDD_FINDINFILES_DIR_COMBO, findHistory._nbMaxFindHistoryPath, findHistory._findHistoryPaths, false);
	saveComboHistory(IDD_FINDINFILES_FILTERS_COMBO, findHistory._nbMaxFindHistoryFilter, findHistory._findHistoryFilters, true);
	saveComboHistory(IDFINDWHAT,                    findHistory._nbMaxFindHistoryFind, findHistory._findHistoryFinds, false);
	saveComboHistory(IDREPLACEWITH,                 findHistory._nbMaxFindHistoryReplace, findHistory._findHistoryReplaces, true);
}

int FindReplaceDlg::saveComboHistory(int id, int maxcount, vector<generic_string> & strings, bool saveEmpty)
{
	TCHAR text[FINDREPLACE_MAXLENGTH] = { '\0' };
	HWND hCombo = ::GetDlgItem(_hSelf, id);
	int count = static_cast<int32_t>(::SendMessage(hCombo, CB_GETCOUNT, 0, 0));
	count = std::min<int>(count, maxcount);

	if (count == CB_ERR) return 0;

	strings.clear();

	if (saveEmpty)
	{
		if (::GetWindowTextLength(hCombo) == 0)
		{
			strings.push_back(generic_string());
		}
	}

	for (int i = 0 ; i < count ; ++i)
	{
		auto cbTextLen = ::SendMessage(hCombo, CB_GETLBTEXTLEN, i, 0);
		if (cbTextLen <= FINDREPLACE_MAXLENGTH - 1)
		{
			::SendMessage(hCombo, CB_GETLBTEXT, i, reinterpret_cast<LPARAM>(text));
			strings.push_back(generic_string(text));
		}
	}
	return count;
}

void FindReplaceDlg::updateCombos()
{
	updateCombo(IDREPLACEWITH);
	updateCombo(IDFINDWHAT);
}

void FindReplaceDlg::updateCombo(int comboID)
{
	HWND hCombo = ::GetDlgItem(_hSelf, comboID);
	addText2Combo(getTextFromCombo(hCombo).c_str(), hCombo);
}

FoundInfo Finder::EmptyFoundInfo(0, 0, 0, TEXT(""));
SearchResultMarkingLine Finder::EmptySearchResultMarking;

bool Finder::notify(SCNotification *notification)
{
	static bool isDoubleClicked = false;

	switch (notification->nmhdr.code)
	{
		case SCN_MARGINCLICK:
			if (notification->margin == ScintillaEditView::_SC_MARGE_FOLDER)
			{
				_scintView.marginClick(notification->position, notification->modifiers);
			}
			break;

		case SCN_DOUBLECLICK:
		{
			// remove selection from the finder
			isDoubleClicked = true;

			// WM_LBUTTONUP can go to a "File not found" messagebox instead of Scintilla here, because the mouse is not captured.
			// The missing message causes mouse cursor flicker as soon as the mouse cursor is moved to a position outside the text editing area.
			::SendMessage(_scintView.getHSelf(), WM_LBUTTONUP, 0, 0);

			size_t pos = notification->position;
			if (static_cast<intptr_t>(pos) == INVALID_POSITION)
				pos = _scintView.execute(SCI_GETLINEENDPOSITION, notification->line);
			_scintView.execute(SCI_SETSEL, pos, pos);

			std::pair<intptr_t, intptr_t> newPos = gotoFoundLine();
			auto lineStartAbsPos = _scintView.execute(SCI_POSITIONFROMLINE, notification->line);
			intptr_t lineEndAbsPos = _scintView.execute(SCI_GETLINEENDPOSITION, notification->line);

			intptr_t begin = newPos.first + lineStartAbsPos;
			intptr_t end = newPos.second + lineStartAbsPos;

			if (end > lineEndAbsPos)
				end = lineEndAbsPos;
			
			_scintView.execute(SCI_SETSEL, begin, end);
			_scintView.execute(SCI_SCROLLRANGE, begin, end);

		}
		break;

		case SCN_PAINTED :
			if (isDoubleClicked)
			{
				(*_ppEditView)->getFocus();
				isDoubleClicked = false;
			}
			break;
	}
	return false;
}


std::pair<intptr_t, intptr_t> Finder::gotoFoundLine(size_t nOccurrence)
{
	std::pair<intptr_t, intptr_t> emptyResult(0, 0);
	auto currentPos = _scintView.execute(SCI_GETCURRENTPOS);
	auto lno = _scintView.execute(SCI_LINEFROMPOSITION, currentPos);
	auto start = _scintView.execute(SCI_POSITIONFROMLINE, lno);
	auto end = _scintView.execute(SCI_GETLINEENDPOSITION, lno);

	if (start + 2 >= end) return emptyResult; // avoid empty lines

	if (_scintView.execute(SCI_GETFOLDLEVEL, lno) & SC_FOLDLEVELHEADERFLAG)
	{
		_scintView.execute(SCI_TOGGLEFOLD, lno);
		return  emptyResult;
	}

	const FoundInfo& fInfo = *(_pMainFoundInfos->begin() + lno);
	const SearchResultMarkingLine& markingLine = *(_pMainMarkings->begin() + lno);

	// Switch to another document
	if (!::SendMessage(_hParent, WM_DOOPEN, 0, reinterpret_cast<LPARAM>(fInfo._fullPath.c_str()))) return emptyResult;

	(*_ppEditView)->_positionRestoreNeeded = false;

	size_t index = 0;

	if (nOccurrence > 0)
	{
		index = nOccurrence - 1;
	}
	else // nOccurrence not used: use current line relative pos to check if it's inside of a marked occurrence
	{
		intptr_t currentPosInLine = currentPos - start;

		for (std::pair<intptr_t, intptr_t> range : markingLine._segmentPostions)
		{
			if (range.first <= currentPosInLine && currentPosInLine <= range.second)
				break;

			++index;
		}
	}

	if (index >= fInfo._ranges.size())
		index = 0;

	Searching::displaySectionCentered(fInfo._ranges[index].first, fInfo._ranges[index].second, *_ppEditView);

	return markingLine._segmentPostions[index];
}

void Finder::deleteResult()
{
	auto currentPos = _scintView.execute(SCI_GETCURRENTPOS); // yniq - add handling deletion of multiple lines?

	auto lno = _scintView.execute(SCI_LINEFROMPOSITION, currentPos);
	auto start = _scintView.execute(SCI_POSITIONFROMLINE, lno);
	auto end = _scintView.execute(SCI_GETLINEENDPOSITION, lno);
	if (start + 2 >= end) return; // avoid empty lines

	if (_scintView.execute(SCI_GETLEXER) == SCLEX_NULL)
	{
		_scintView.setLexer(L_SEARCHRESULT, LIST_NONE); // Restore searchResult lexer in case the lexer was changed to SCLEX_NULL in GotoFoundLine()
	}

	if (_scintView.execute(SCI_GETFOLDLEVEL, lno) & SC_FOLDLEVELHEADERFLAG)  // delete a folder
	{
		auto endline = _scintView.execute(SCI_GETLASTCHILD, lno, -1) + 1;
		assert((size_t) endline <= _pMainFoundInfos->size());

		_pMainFoundInfos->erase(_pMainFoundInfos->begin() + lno, _pMainFoundInfos->begin() + endline); // remove found info
		_pMainMarkings->erase(_pMainMarkings->begin() + lno, _pMainMarkings->begin() + endline);

		auto end2 = _scintView.execute(SCI_POSITIONFROMLINE, endline);
		_scintView.execute(SCI_SETSEL, start, end2);
		setFinderReadOnly(false);
		_scintView.execute(SCI_CLEAR);
		setFinderReadOnly(true);
	}
	else // delete one line
	{
		assert((size_t) lno < _pMainFoundInfos->size());

		_pMainFoundInfos->erase(_pMainFoundInfos->begin() + lno); // remove found info
		_pMainMarkings->erase(_pMainMarkings->begin() + lno);

		setFinderReadOnly(false);
		_scintView.execute(SCI_LINEDELETE);
		setFinderReadOnly(true);
	}
	_markingsStruct._length = static_cast<long>(_pMainMarkings->size());

	assert(_pMainFoundInfos->size() == _pMainMarkings->size());
	assert(size_t(_scintView.execute(SCI_GETLINECOUNT)) == _pMainFoundInfos->size() + 1);
}

vector<generic_string> Finder::getResultFilePaths() const
{
	vector<generic_string> paths;
	size_t len = _pMainFoundInfos->size();
	for (size_t i = 0; i < len; ++i)
	{
		// make sure that path is not already in
		generic_string & path2add = (*_pMainFoundInfos)[i]._fullPath;
		bool found = path2add.empty();
		for (size_t j = 0; j < paths.size() && !found; ++j)
		{
			if (paths[j] == path2add)
				found = true;

		}
		if (!found)
			paths.push_back(path2add);
	}
	return paths;
}

bool Finder::canFind(const TCHAR *fileName, size_t lineNumber, size_t* indexToStartFrom) const
{
	size_t len = _pMainFoundInfos->size();
	for (size_t i = *indexToStartFrom; i < len; ++i)
	{
		if ((*_pMainFoundInfos)[i]._fullPath == fileName)
		{
			if (lineNumber == (*_pMainFoundInfos)[i]._lineNumber)
			{
				*indexToStartFrom = i;
				return true;
			}
		}
	}
	return false;
}

// Y     : current pos
// X     : current sel
// XXXXY : current sel + current pos
// 
//      1          2                3        4                   Status      auxiliaryInfo
// =======================================================================================
// Y [     ]    [     ]          [     ]  [     ]            : pos_infront        -1
//   [     ]    [     ]          [     ]  [  Y  ]            : pos_inside          4
//   [     ]  XXY     ]          [     ]  [     ]            : pos_inside          2
//   [     ]    [     ]          [     ]  [  XXXY            : pos_inside          4
//   [     ]    [     ]          [XXXXXY  [     ]            : pos_inside          3
//   [     Y    [     ]          [     ]  [     ]            : pos_between         1
//   [     ]    [     ]          [     ]  Y     ]            : pos_between         3
//   [     ]    [     ]          [     Y  [     ]            : pos_between         3
//   [     ]    [     ]  Y       [     ]  [     ]            : pos_between         2
//   [     ]    [     ]          [     ]  [     ]    Y       : pos_behind          4

Finder::CurrentPosInLineInfo Finder::getCurrentPosInLineInfo(intptr_t currentPosInLine, const SearchResultMarkingLine& markingLine) const
{
	CurrentPosInLineInfo cpili;
	size_t count = 0;
	intptr_t lastEnd = 0;
	auto selStart = _scintView.execute(SCI_GETSELECTIONSTART);
	auto selEnd = _scintView.execute(SCI_GETSELECTIONEND);
	bool hasSel = (selEnd - selStart) != 0;

	for (std::pair<intptr_t, intptr_t> range : markingLine._segmentPostions)
	{
		++count;

		if (lastEnd <= currentPosInLine && currentPosInLine < range.first)
		{
			if (count == 1)
			{
				cpili._status = pos_infront;
				break;
			}
			else
			{
				cpili._status = pos_between;
				cpili.auxiliaryInfo = count - 1;
				break;
			}
		}
		
		if (range.first <= currentPosInLine && currentPosInLine <= range.second)
		{
			if (currentPosInLine == range.first && !hasSel)
			{
				cpili._status = pos_between;
				cpili.auxiliaryInfo = count - 1; //  c1      c2
				                                 // [   ]  I[   ]      : I is recongnized with c2, so auxiliaryInfo should be c1 (c2-1)
			}
			else if (currentPosInLine == range.second && !hasSel)
			{
				cpili._status = pos_between;
				cpili.auxiliaryInfo = count;     //  c1      c2
				                                 // [   ]I  [   ]      : I is recongnized with c1, so auxiliaryInfo should be c1
			}
			else
			{
				cpili._status = pos_inside;
				cpili.auxiliaryInfo = count;
			}
			break;
		}

		if (range.second < currentPosInLine)
		{
			if (markingLine._segmentPostions.size() == count)
			{
				cpili._status = pos_behind;
				cpili.auxiliaryInfo = count;
				break;
			}
		}

		lastEnd = range.second;
	}

	return cpili;
}

void Finder::anchorWithNoHeaderLines(intptr_t& currentL, intptr_t initL, intptr_t minL, intptr_t maxL, int direction)
{
	if (currentL > maxL && direction == 0)
		currentL = minL;

	while (_scintView.execute(SCI_GETFOLDLEVEL, currentL) & SC_FOLDLEVELHEADERFLAG)
	{
		currentL += direction == -1 ? -1 : 1;

		if (currentL > maxL)
			currentL = minL;
		else if (currentL < minL)
			currentL = maxL;

		if (currentL == initL)
			break;
	}

	auto extremityAbsoltePos = _scintView.execute(direction == -1 ? SCI_GETLINEENDPOSITION : SCI_POSITIONFROMLINE, currentL);
	_scintView.execute(SCI_SETSEL, extremityAbsoltePos, extremityAbsoltePos);
}

void Finder::gotoNextFoundResult(int direction)
{
	//
	// Get currentLine & currentPosInLine from CurrentPos
	//
	auto currentPos = _scintView.execute(SCI_GETCURRENTPOS);
	intptr_t lno = _scintView.execute(SCI_LINEFROMPOSITION, currentPos);
	auto total_lines = _scintView.execute(SCI_GETLINECOUNT);
	if (total_lines <= 1) return;

	auto lineStartAbsPos = _scintView.execute(SCI_POSITIONFROMLINE, lno);
	intptr_t currentPosInLine = currentPos - lineStartAbsPos;

	auto init_lno = lno;
	auto max_lno = _scintView.execute(SCI_GETLASTCHILD, lno, searchHeaderLevel);

	assert(max_lno <= total_lines - 2);

	// get the line number of the current search (searchHeaderLevel)
	int level = _scintView.execute(SCI_GETFOLDLEVEL, lno) & SC_FOLDLEVELNUMBERMASK;
	auto min_lno = lno;
	while (level-- >= fileHeaderLevel)
	{
		min_lno = _scintView.execute(SCI_GETFOLDPARENT, min_lno);
		assert(min_lno >= 0);
	}

	if (min_lno < 0) min_lno = lno; // when lno is a search header line

	assert(min_lno <= max_lno);

	if (lno > max_lno && direction == 0) lno = min_lno;
	else if (lno < min_lno) lno = max_lno;

	//
	// Set anchor and make sure that achor is not on the last (empty) line or head lines
	//
	while (_scintView.execute(SCI_GETFOLDLEVEL, lno) & SC_FOLDLEVELHEADERFLAG)
	{
		lno += direction == -1 ? -1 : 1;

		if (lno > max_lno)
			lno = min_lno;
		else if (lno < min_lno)
			lno = max_lno;

		if (lno == init_lno)
			break;
	}

	if (lno != init_lno)
	{
		auto extremityAbsoltePos = _scintView.execute(direction == -1 ? SCI_GETLINEENDPOSITION : SCI_POSITIONFROMLINE, lno);
		_scintView.execute(SCI_SETSEL, extremityAbsoltePos, extremityAbsoltePos);
		currentPos = extremityAbsoltePos;
		auto start = _scintView.execute(SCI_POSITIONFROMLINE, lno);
		currentPosInLine = currentPos - start;
	}


	size_t n = 0;
	const SearchResultMarkingLine& markingLine = *(_pMainMarkings->begin() + lno);

	//
	// Determinate currentPosInLine status among pos_infront, pose_between, pos_inside and pos_behind
	//
	CurrentPosInLineInfo cpili  = getCurrentPosInLineInfo(currentPosInLine, markingLine);

	//
	// According CurrentPosInLineInfo and direction, set position and get number of occurrence
	//
	if (direction == 0) // Next
	{
		switch (cpili._status)
		{
			case pos_infront:
			{
				n = 1;
			}
			break;

			case pos_between:
			case pos_inside:
			{
				n = cpili.auxiliaryInfo + 1;
				if (n > markingLine._segmentPostions.size())
				{
					lno++;
					anchorWithNoHeaderLines(lno, init_lno, min_lno, max_lno, direction);
					n = 1;
				}
			}
			break;

			case pos_behind:
			{
				lno++;
				anchorWithNoHeaderLines(lno, init_lno, min_lno, max_lno, direction);
				n = 1;
			}
			break;
		}
	}
	else if (direction == -1) // Previous
	{
		switch (cpili._status)
		{
			case pos_infront:
			{
				lno--;
				anchorWithNoHeaderLines(lno, init_lno, min_lno, max_lno, direction);
				const SearchResultMarkingLine& newMarkingLine = *(_pMainMarkings->begin() + lno);
				n = newMarkingLine._segmentPostions.size();
			}
			break;

			case pos_between:
			{
				n = cpili.auxiliaryInfo;
			}
			break;

			case pos_inside:
			{
				if (cpili.auxiliaryInfo > 1)
					n = cpili.auxiliaryInfo - 1;
				else
				{
					lno--;
					anchorWithNoHeaderLines(lno, init_lno, min_lno, max_lno, direction);
					const SearchResultMarkingLine& newMarkingLine = *(_pMainMarkings->begin() + lno);
					n = newMarkingLine._segmentPostions.size();
				}
			}
			break;

			case pos_behind:
			{
				n = cpili.auxiliaryInfo;
			}
			break;
		}
	}
	else // invalid
	{
		return;
	}

	_scintView.execute(SCI_ENSUREVISIBLE, lno);
	std::pair<intptr_t, intptr_t> newPos = gotoFoundLine(n);

	lineStartAbsPos = _scintView.execute(SCI_POSITIONFROMLINE, lno);
	intptr_t lineEndAbsPos = _scintView.execute(SCI_GETLINEENDPOSITION, lno);

	intptr_t begin = newPos.first + lineStartAbsPos;
	intptr_t end = newPos.second + lineStartAbsPos;

	if (end > lineEndAbsPos)
		end = lineEndAbsPos;

	_scintView.execute(SCI_SETSEL, begin, end);
	_scintView.execute(SCI_SCROLLRANGE, begin, end);

}

void FindInFinderDlg::initFromOptions()
{
	HWND hFindCombo = ::GetDlgItem(_hSelf, IDFINDWHAT_FIFOLDER);
	addText2Combo(_options._str2Search.c_str(), hFindCombo);

	setChecked(IDC_MATCHLINENUM_CHECK_FIFOLDER, _options._isMatchLineNumber);

	setChecked(IDWHOLEWORD_FIFOLDER, _options._searchType != FindRegex && _options._isWholeWord);
	::EnableWindow(::GetDlgItem(_hSelf, IDWHOLEWORD_FIFOLDER), _options._searchType != FindRegex);

	setChecked(IDMATCHCASE_FIFOLDER, _options._isMatchCase);

	setChecked(IDNORMAL_FIFOLDER, _options._searchType == FindNormal);
	setChecked(IDEXTENDED_FIFOLDER, _options._searchType == FindExtended);
	setChecked(IDREGEXP_FIFOLDER, _options._searchType == FindRegex);

	setChecked(IDREDOTMATCHNL_FIFOLDER, _options._dotMatchesNewline);
	::EnableWindow(::GetDlgItem(_hSelf, IDREDOTMATCHNL_FIFOLDER), _options._searchType == FindRegex);
}

void FindInFinderDlg::writeOptions()
{
	HWND hFindCombo = ::GetDlgItem(_hSelf, IDFINDWHAT_FIFOLDER);
	_options._str2Search = getTextFromCombo(hFindCombo);
	_options._isMatchLineNumber = isCheckedOrNot(IDC_MATCHLINENUM_CHECK_FIFOLDER);
	_options._isWholeWord = isCheckedOrNot(IDWHOLEWORD_FIFOLDER);
	_options._isMatchCase = isCheckedOrNot(IDMATCHCASE_FIFOLDER);
	_options._searchType = isCheckedOrNot(IDREGEXP_FIFOLDER) ? FindRegex : isCheckedOrNot(IDEXTENDED_FIFOLDER) ? FindExtended : FindNormal;
	_options._dotMatchesNewline = isCheckedOrNot(IDREDOTMATCHNL_FIFOLDER);
}

intptr_t CALLBACK FindInFinderDlg::run_dlgProc(UINT message, WPARAM wParam, LPARAM /*lParam*/)
{
	switch (message)
	{
		case WM_INITDIALOG:
		{
			NativeLangSpeaker *pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();
			pNativeSpeaker->changeDlgLang(_hSelf, "FindInFinder");

			NppDarkMode::autoSubclassAndThemeChildControls(_hSelf);

			initFromOptions();

			goToCenter(SWP_SHOWWINDOW | SWP_NOSIZE);
		}
		return TRUE;

		case WM_CTLCOLOREDIT:
		{
			return NppDarkMode::onCtlColorSofter(reinterpret_cast<HDC>(wParam));
		}

		case WM_CTLCOLORLISTBOX:
		{
			return NppDarkMode::onCtlColor(reinterpret_cast<HDC>(wParam));
		}

		case WM_CTLCOLORDLG:
		case WM_CTLCOLORSTATIC:
		{
			return NppDarkMode::onCtlColorDarker(reinterpret_cast<HDC>(wParam));
		}

		case WM_PRINTCLIENT:
		{
			if (NppDarkMode::isEnabled())
			{
				return TRUE;
			}
			break;
		}

		case WM_ERASEBKGND:
		{
			if (NppDarkMode::isEnabled())
			{
				RECT rc{};
				getClientRect(rc);
				::FillRect(reinterpret_cast<HDC>(wParam), &rc, NppDarkMode::getDarkerBackgroundBrush());
				return TRUE;
			}
			break;
		}

		case NPPM_INTERNAL_REFRESHDARKMODE:
		{
			NppDarkMode::autoThemeChildControls(_hSelf);
			return TRUE;
		}

		case WM_COMMAND:
		{
			switch (LOWORD(wParam))
			{
				case IDCANCEL:
				{
					::EndDialog(_hSelf, -1);
					return TRUE;
				}

				case IDOK:
				{
					writeOptions();
					::EndDialog(_hSelf, -1);
					FindersInfo findersInfo;
					findersInfo._pSourceFinder = _pFinder2Search;
					findersInfo._findOption = _options;
					::SendMessage(_hParent, WM_FINDALL_INCURRENTFINDER, reinterpret_cast<WPARAM>(&findersInfo), 0);
					return TRUE;
				}

				case IDNORMAL_FIFOLDER:
				case IDEXTENDED_FIFOLDER:
				case IDREGEXP_FIFOLDER:
				{
					if (isCheckedOrNot(IDREGEXP_FIFOLDER))
					{
						::EnableWindow(::GetDlgItem(_hSelf, IDWHOLEWORD_FIFOLDER), false);
						setChecked(IDWHOLEWORD_FIFOLDER, false);
						::EnableWindow(GetDlgItem(_hSelf, IDREDOTMATCHNL_FIFOLDER), true);
					}
					else if (isCheckedOrNot(IDEXTENDED_FIFOLDER))
					{
						::EnableWindow(::GetDlgItem(_hSelf, IDWHOLEWORD_FIFOLDER), true);
						::EnableWindow(GetDlgItem(_hSelf, IDREDOTMATCHNL_FIFOLDER), false);
					}
					else
					{
						// normal mode
						::EnableWindow(::GetDlgItem(_hSelf, IDWHOLEWORD_FIFOLDER), true);
						::EnableWindow(GetDlgItem(_hSelf, IDREDOTMATCHNL_FIFOLDER), false);
					}

					return TRUE;
				}
			}
			return FALSE;
		}
		default:
			return FALSE;
	}
	return FALSE;
}


void FindReplaceDlg::resizeDialogElements(LONG newWidth)
{
	//elements that need to be resized horizontally (all edit/combo boxes etc.)
	const auto resizeWindowIDs = { IDFINDWHAT, IDREPLACEWITH, IDD_FINDINFILES_FILTERS_COMBO, IDD_FINDINFILES_DIR_COMBO };

	//elements that need to be moved
	const auto moveWindowIDs = {
		IDD_FINDINFILES_FOLDERFOLLOWSDOC_CHECK,IDD_FINDINFILES_RECURSIVE_CHECK, IDD_FINDINFILES_INHIDDENDIR_CHECK,
		IDD_FINDINFILES_PROJECT1_CHECK, IDD_FINDINFILES_PROJECT2_CHECK, IDD_FINDINFILES_PROJECT3_CHECK,
		IDC_TRANSPARENT_GRPBOX, IDC_TRANSPARENT_CHECK, IDC_TRANSPARENT_LOSSFOCUS_RADIO, IDC_TRANSPARENT_ALWAYS_RADIO,
		IDC_PERCENTAGE_SLIDER , IDC_REPLACEINSELECTION , IDC_IN_SELECTION_CHECK,

		IDD_FINDINFILES_BROWSE_BUTTON, IDCMARKALL, IDC_CLEAR_ALL, IDCCOUNTALL, IDC_FINDALL_OPENEDFILES, IDC_FINDALL_CURRENTFILE,
		IDREPLACE, IDREPLACEALL, IDD_FINDREPLACE_SWAP_BUTTON, IDC_REPLACE_OPENEDFILES, IDD_FINDINFILES_FIND_BUTTON, IDD_FINDINFILES_REPLACEINFILES, IDOK, IDCANCEL,
		IDC_FINDPREV, IDC_FINDNEXT, IDC_2_BUTTONS_MODE, IDC_COPY_MARKED_TEXT, IDD_FINDINFILES_REPLACEINPROJECTS, IDD_RESIZE_TOGGLE_BUTTON
	};

	const UINT flags = SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE | SWP_NOCOPYBITS;

	auto newDeltaWidth = newWidth - _initialClientWidth;
	auto addWidth = newDeltaWidth - _deltaWidth;
	_deltaWidth = newDeltaWidth;

	RECT rc;
	for (int id : resizeWindowIDs)
	{
		HWND resizeHwnd = ::GetDlgItem(_hSelf, id);
		::GetClientRect(resizeHwnd, &rc);

		// Combo box for some reasons selects text on resize. So let's check befor resize if selection is present and clear it manually after resize.
		DWORD endSelection = 0;
		SendMessage(resizeHwnd, CB_GETEDITSEL, 0, (LPARAM)&endSelection);

		::SetWindowPos(resizeHwnd, NULL, 0, 0, rc.right + addWidth, rc.bottom, SWP_NOMOVE | flags);

		if (endSelection == 0)
		{
			SendMessage(resizeHwnd, CB_SETEDITSEL, 0, 0);
		}
	}

	for (int moveWndID : moveWindowIDs)
	{
		HWND moveHwnd = GetDlgItem(_hSelf, moveWndID);
		::GetWindowRect(moveHwnd, &rc);
		::MapWindowPoints(NULL, _hSelf, (LPPOINT)&rc, 2);

		::SetWindowPos(moveHwnd, NULL, rc.left + addWidth, rc.top, 0, 0, SWP_NOSIZE | flags);
	}

	auto additionalWindowHwndsToResize = { _tab.getHSelf() , _statusBar.getHSelf() };

	for (HWND resizeHwnd : additionalWindowHwndsToResize)
	{
		::GetClientRect(resizeHwnd, &rc);
		::SetWindowPos(resizeHwnd, NULL, 0, 0, rc.right + addWidth, rc.bottom, SWP_NOMOVE | flags);
	}
}

std::mutex findOps_mutex;

intptr_t CALLBACK FindReplaceDlg::run_dlgProc(UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
		case WM_GETMINMAXINFO:
		{
			bool isLessModeOn = NppParameters::getInstance().getNppGUI()._findWindowLessMode;
			MINMAXINFO* mmi = reinterpret_cast<MINMAXINFO*>(lParam);
			mmi->ptMinTrackSize.y = isLessModeOn ? _lesssModeHeight : _initialWindowRect.bottom;
			mmi->ptMinTrackSize.x = _initialWindowRect.right;
			mmi->ptMaxTrackSize.y = isLessModeOn ? _lesssModeHeight : _initialWindowRect.bottom;

			return 0;
		}

		case WM_SIZE:
		{
			resizeDialogElements(LOWORD(lParam));
			return TRUE;
		}

		case WM_CTLCOLOREDIT:
		{
			return NppDarkMode::onCtlColorSofter(reinterpret_cast<HDC>(wParam));
		}

		case WM_CTLCOLORLISTBOX:
		{
			return NppDarkMode::onCtlColor(reinterpret_cast<HDC>(wParam));
		}

		case WM_CTLCOLORDLG:
		case WM_CTLCOLORSTATIC:
		{
			return NppDarkMode::onCtlColorDarker(reinterpret_cast<HDC>(wParam));
		}

		case WM_PRINTCLIENT:
		{
			if (NppDarkMode::isEnabled())
			{
				return TRUE;
			}
			break;
		}

		case WM_ERASEBKGND:
		{
			if (NppDarkMode::isEnabled())
			{
				RECT rc{};
				getClientRect(rc);
				::FillRect(reinterpret_cast<HDC>(wParam), &rc, NppDarkMode::getDarkerBackgroundBrush());
				return TRUE;
			}
			break;
		}

		case NPPM_INTERNAL_REFRESHDARKMODE:
		{
			NppDarkMode::setDarkTooltips(_shiftTrickUpTip, NppDarkMode::ToolTipsType::tooltip);
			NppDarkMode::setDarkTooltips(_2ButtonsTip, NppDarkMode::ToolTipsType::tooltip);
			NppDarkMode::setDarkTooltips(_filterTip, NppDarkMode::ToolTipsType::tooltip);

			if (_statusbarTooltipWnd)
			{
				NppDarkMode::setDarkTooltips(_statusbarTooltipWnd, NppDarkMode::ToolTipsType::tooltip);
			}

			HWND finder = getHFindResults();
			if (finder)
			{
				NppDarkMode::setDarkScrollBar(finder);
			}

			NppDarkMode::autoThemeChildControls(_hSelf);
			return TRUE;
		}

		case WM_INITDIALOG :
		{
			NppDarkMode::autoSubclassAndThemeChildControls(_hSelf);

			HWND hFindCombo = ::GetDlgItem(_hSelf, IDFINDWHAT);
			HWND hReplaceCombo = ::GetDlgItem(_hSelf, IDREPLACEWITH);
			HWND hFiltersCombo = ::GetDlgItem(_hSelf, IDD_FINDINFILES_FILTERS_COMBO);
			HWND hDirCombo = ::GetDlgItem(_hSelf, IDD_FINDINFILES_DIR_COMBO);

			// Change handler of edit element in the comboboxes to support Ctrl+Backspace
			COMBOBOXINFO cbinfo{};
			cbinfo.cbSize = sizeof(COMBOBOXINFO);
			GetComboBoxInfo(hFindCombo, &cbinfo);
			if (!cbinfo.hwndItem) return FALSE;

			originalComboEditProc = reinterpret_cast<WNDPROC>(SetWindowLongPtr(cbinfo.hwndItem, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(comboEditProc)));
			SetWindowLongPtr(cbinfo.hwndItem, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(cbinfo.hwndCombo));
			GetComboBoxInfo(hReplaceCombo, &cbinfo);
			SetWindowLongPtr(cbinfo.hwndItem, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(comboEditProc));
			SetWindowLongPtr(cbinfo.hwndItem, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(cbinfo.hwndCombo));
			GetComboBoxInfo(hFiltersCombo, &cbinfo);
			SetWindowLongPtr(cbinfo.hwndItem, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(comboEditProc));
			SetWindowLongPtr(cbinfo.hwndItem, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(cbinfo.hwndCombo));
			GetComboBoxInfo(hDirCombo, &cbinfo);
			SetWindowLongPtr(cbinfo.hwndItem, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(comboEditProc));
			SetWindowLongPtr(cbinfo.hwndItem, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(cbinfo.hwndCombo));

			if ((NppParameters::getInstance()).getNppGUI()._monospacedFontFindDlg)
			{
				_hMonospaceFont = createFont(TEXT("Courier New"), 8, false, _hSelf);

				SendMessage(hFindCombo, WM_SETFONT, (WPARAM)_hMonospaceFont, MAKELPARAM(true, 0));
				SendMessage(hReplaceCombo, WM_SETFONT, (WPARAM)_hMonospaceFont, MAKELPARAM(true, 0));
				SendMessage(hFiltersCombo, WM_SETFONT, (WPARAM)_hMonospaceFont, MAKELPARAM(true, 0));
				SendMessage(hDirCombo, WM_SETFONT, (WPARAM)_hMonospaceFont, MAKELPARAM(true, 0));
			}

			DPIManager& dpiManager = NppParameters::getInstance()._dpiManager;

			// Change ComboBox height to accomodate High-DPI settings.
			// ComboBoxes are scaled using the font used in them, however this results in weird optics
			// on scaling > 200% (192 DPI). Using this method we accomodate these scalings way better
			// than the OS does with the current dpiAware.manifest...
			for (HWND hComboBox : { hFindCombo, hReplaceCombo, hFiltersCombo, hDirCombo })
			{
				LOGFONT lf = {};
				HFONT font = reinterpret_cast<HFONT>(SendMessage(hComboBox, WM_GETFONT, 0, 0));
				::GetObject(font, sizeof(lf), &lf);
				lf.lfHeight = (dpiManager.scaleY(16) - 5) * -1;
				SendMessage(hComboBox, WM_SETFONT, (WPARAM)CreateFontIndirect(&lf), MAKELPARAM(true, 0));
			}

			RECT arc{};
			::GetWindowRect(::GetDlgItem(_hSelf, IDCANCEL), &arc);
			_markClosePos.bottom = _findInFilesClosePos.bottom = _replaceClosePos.bottom = _findClosePos.bottom = arc.bottom - arc.top;
			_markClosePos.right = _findInFilesClosePos.right = _replaceClosePos.right = _findClosePos.right = arc.right - arc.left;

			POINT p{};
			p.x = arc.left;
			p.y = arc.top;
			::ScreenToClient(_hSelf, &p);

			p = getTopPoint(::GetDlgItem(_hSelf, IDCANCEL), !_isRTL);
			_replaceClosePos.left = p.x;
			_replaceClosePos.top = p.y;

			 p = getTopPoint(::GetDlgItem(_hSelf, IDREPLACEALL), !_isRTL);
			 _findInFilesClosePos.left = p.x;
			 _findInFilesClosePos.top = p.y;

			 p = getTopPoint(::GetDlgItem(_hSelf, IDC_REPLACE_OPENEDFILES), !_isRTL);
			 _markClosePos.left = p.x;
			 _markClosePos.top = p.y;

			 p = getTopPoint(::GetDlgItem(_hSelf, IDCANCEL), !_isRTL);
			 _findClosePos.left = p.x;
			 _findClosePos.top = p.y + 10;

			 ::GetWindowRect(::GetDlgItem(_hSelf, IDD_RESIZE_TOGGLE_BUTTON), &arc);
			 long resizeButtonW = arc.right - arc.left;
			 long resizeButtonH = arc.bottom - arc.top;
			 _collapseButtonPos.bottom = _uncollapseButtonPos.bottom = resizeButtonW;
			 _collapseButtonPos.right = _uncollapseButtonPos.right = resizeButtonH;

			 ::GetWindowRect(::GetDlgItem(_hSelf, IDC_TRANSPARENT_GRPBOX), &arc);
			 p = getTopPoint(::GetDlgItem(_hSelf, IDC_TRANSPARENT_GRPBOX), !_isRTL);
			 _collapseButtonPos.left = p.x + (arc.right - arc.left) + dpiManager.scaleX(10);
			 _collapseButtonPos.top = p.y + (arc.bottom - arc.top) - resizeButtonH + dpiManager.scaleX(10);
			 ::GetWindowRect(::GetDlgItem(_hSelf, IDCCOUNTALL), &arc);
			 p = getTopPoint(::GetDlgItem(_hSelf, IDCCOUNTALL), !_isRTL);
			 _uncollapseButtonPos.left = p.x + (arc.right - arc.left) + dpiManager.scaleX(8);
			 _uncollapseButtonPos.top = p.y + dpiManager.scaleY(2);
			 
			 // in selection check
			 RECT checkRect;
			 ::GetWindowRect(::GetDlgItem(_hSelf, IDC_IN_SELECTION_CHECK), &checkRect);
			 _countInSelCheckPos.bottom = _replaceInSelCheckPos.bottom = checkRect.bottom - checkRect.top;
			 _countInSelCheckPos.right = _replaceInSelCheckPos.right = checkRect.right - checkRect.left;

			 p = getTopPoint(::GetDlgItem(_hSelf, IDC_IN_SELECTION_CHECK), !_isRTL);
			 _countInSelCheckPos.left = _replaceInSelCheckPos.left = p.x;
			 _countInSelCheckPos.top = _replaceInSelCheckPos.top = p.y;

			 POINT countP = getTopPoint(::GetDlgItem(_hSelf, IDCCOUNTALL), !_isRTL);

			 // in selection Frame
			 RECT frameRect;
			 ::GetWindowRect(::GetDlgItem(_hSelf, IDC_REPLACEINSELECTION), &frameRect);
			 _countInSelFramePos.bottom = _replaceInSelFramePos.bottom = frameRect.bottom - frameRect.top;
			 _countInSelFramePos.right = _replaceInSelFramePos.right = frameRect.right - frameRect.left;

			 p = getTopPoint(::GetDlgItem(_hSelf, IDC_REPLACEINSELECTION), !_isRTL);
			 _countInSelFramePos.left = _replaceInSelFramePos.left = p.x;
			 _countInSelFramePos.top = _replaceInSelFramePos.top = p.y;

			 _countInSelFramePos.top = countP.y - dpiManager.scaleY(10);
			 _countInSelFramePos.bottom = dpiManager.scaleY(80 - 3);

			 NativeLangSpeaker *pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();

			 generic_string searchButtonTip = pNativeSpeaker->getLocalizedStrFromID("shift-change-direction-tip", TEXT("Use Shift+Enter to search in the opposite direction."));
			 _shiftTrickUpTip = CreateToolTip(IDOK, _hSelf, _hInst, const_cast<PTSTR>(searchButtonTip.c_str()), _isRTL);

			 generic_string checkboxTip = pNativeSpeaker->getLocalizedStrFromID("two-find-buttons-tip", TEXT("2 find buttons mode"));
			 _2ButtonsTip = CreateToolTip(IDC_2_BUTTONS_MODE, _hSelf, _hInst, const_cast<PTSTR>(checkboxTip.c_str()), _isRTL);

			 generic_string findInFilesFilterTip = pNativeSpeaker->getLocalizedStrFromID("find-in-files-filter-tip", TEXT("Find in cpp, cxx, h, hxx && hpp:\r*.cpp *.cxx *.h *.hxx *.hpp\r\rFind in all files except exe, obj && log:\r*.* !*.exe !*.obj !*.log\r\rFind in all files but exclude folders tests, bin && bin64:\r*.* !\\tests !\\bin*\r\rFind in all files but exclude all folders log or logs recursively:\r*.* !+\\log*"));
			 _filterTip = CreateToolTip(IDD_FINDINFILES_FILTERS_STATIC, _hSelf, _hInst, const_cast<PTSTR>(findInFilesFilterTip.c_str()), _isRTL);

			::SetWindowTextW(::GetDlgItem(_hSelf, IDC_FINDPREV), TEXT("▲"));
			::SetWindowTextW(::GetDlgItem(_hSelf, IDC_FINDNEXT), TEXT("▼ Find Next"));

			_hSwapButton = ::GetDlgItem(_hSelf, IDD_FINDREPLACE_SWAP_BUTTON);
			::SetWindowLongPtr(_hSwapButton, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
			_oldSwapButtonProc = reinterpret_cast<WNDPROC>(::SetWindowLongPtr(_hSwapButton, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(swapButtonProc)));
			::SetWindowTextW(_hSwapButton, TEXT("⇅"));

			::SetWindowTextW(::GetDlgItem(_hSelf, IDD_RESIZE_TOGGLE_BUTTON), TEXT("˄"));

			// "⇅" enlargement
			_hLargerBolderFont = createFont(TEXT("Courier New"), 14, true, _hSelf);
			SendMessage(_hSwapButton, WM_SETFONT, (WPARAM)_hLargerBolderFont, MAKELPARAM(true, 0));

			// Make "˄" & "˅" look better
			_hCourrierNewFont = createFont(TEXT("Courier New"), 12, false, _hSelf);
			SendMessage(::GetDlgItem(_hSelf, IDD_RESIZE_TOGGLE_BUTTON), WM_SETFONT, (WPARAM)_hCourrierNewFont, MAKELPARAM(true, 0));

			return TRUE;
		}

		case WM_DRAWITEM :
		{
			drawItem((DRAWITEMSTRUCT *)lParam);
			return TRUE;
		}

		case WM_HSCROLL :
		{
			if (reinterpret_cast<HWND>(lParam) == ::GetDlgItem(_hSelf, IDC_PERCENTAGE_SLIDER))
			{
				int percent = static_cast<int32_t>(::SendDlgItemMessage(_hSelf, IDC_PERCENTAGE_SLIDER, TBM_GETPOS, 0, 0));
				FindHistory & findHistory = (NppParameters::getInstance()).getFindHistory();
				findHistory._transparency = percent;
				if (isCheckedOrNot(IDC_TRANSPARENT_ALWAYS_RADIO))
				{
					(NppParameters::getInstance()).SetTransparent(_hSelf, percent);
				}
			}
			return TRUE;
		}

		case WM_NOTIFY:
		{
			auto lpnmhdr = reinterpret_cast<LPNMHDR>(lParam);
			switch (lpnmhdr->code)
			{
				case TCN_SELCHANGE:
				{
					const HWND tabHandle = _tab.getHSelf();
					if (lpnmhdr->hwndFrom == tabHandle)
					{
						const auto indexClicked = static_cast<int>(::SendMessage(tabHandle, TCM_GETCURSEL, 0, 0));
						doDialog(static_cast<DIALOG_TYPE>(indexClicked));
						return TRUE;
					}
					break;
				}

				case BCN_DROPDOWN:
				{
					if (lpnmhdr->hwndFrom == _hSwapButton)
					{
						if (!_swapPopupMenu.isCreated())
						{
							vector<MenuItemUnit> itemUnitArray;
							itemUnitArray.push_back(MenuItemUnit(IDC_SWAP_FIND_REPLACE, TEXT("⇅ Swap Find with Replace")));
							itemUnitArray.push_back(MenuItemUnit(IDC_COPY_FIND2REPLACE, TEXT("⤵ Copy from Find to Replace")));
							itemUnitArray.push_back(MenuItemUnit(IDC_COPY_REPLACE2FIND, TEXT("⤴ Copy from Replace to Find")));

							NativeLangSpeaker* pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();
							for (auto&& i : itemUnitArray)
							{
								i._itemName = pNativeSpeaker->getDlgLangMenuStr("Dialog", "Find", i._cmdID, i._itemName.c_str());
							}

							_swapPopupMenu.create(_hSelf, itemUnitArray);
						}

						RECT rc{};
						::GetClientRect(_hSwapButton, &rc);
						POINT p{};
						::ClientToScreen(_hSwapButton, &p);
						p.y += rc.bottom;
						_swapPopupMenu.display(p);

						return TRUE;
					}
					break;
				}
			}
			return FALSE;
		}

		case WM_ACTIVATE :
		{
			if (LOWORD(wParam) == WA_ACTIVE || LOWORD(wParam) == WA_CLICKACTIVE)
			{
				Sci_CharacterRangeFull cr = (*_ppEditView)->getSelection();
				intptr_t nbSelected = (*_ppEditView)->execute(SCI_COUNTCHARACTERS, cr.cpMin, cr.cpMax);

				bool inSelEnabled = nbSelected != 0;

				// Searching/replacing in multiple selections or column selection is not allowed
				if (((*_ppEditView)->execute(SCI_GETSELECTIONMODE) == SC_SEL_RECTANGLE) ||
					((*_ppEditView)->execute(SCI_GETSELECTIONS) > 1))
				{
					inSelEnabled = false;
				}

				enableFindDlgItem(IDC_IN_SELECTION_CHECK, inSelEnabled);

				bool inSelChecked = isCheckedOrNot(IDC_IN_SELECTION_CHECK);

				const NppGUI& nppGui = (NppParameters::getInstance()).getNppGUI();
				if (nppGui._inSelectionAutocheckThreshold != 0)
				{
					// code is allowed to change checkmark status of In-selection checkbox

					inSelChecked = inSelEnabled && (nbSelected >= nppGui._inSelectionAutocheckThreshold);

					setChecked(IDC_IN_SELECTION_CHECK, inSelChecked);
				}

				_options._isInSelection = inSelEnabled && inSelChecked;
			}

			if (isCheckedOrNot(IDC_TRANSPARENT_LOSSFOCUS_RADIO))
			{
				if (LOWORD(wParam) == WA_INACTIVE && isVisible())
				{
					int percent = static_cast<int32_t>(::SendDlgItemMessage(_hSelf, IDC_PERCENTAGE_SLIDER, TBM_GETPOS, 0, 0));
					(NppParameters::getInstance()).SetTransparent(_hSelf, percent);
				}
				else
				{
					(NppParameters::getInstance()).removeTransparent(_hSelf);
				}
			}

			// At very first time (when find dlg is launched), search mode is Normal.
			// In that case, ". Matches newline" should be disabled as it applicable only for Regex
			if (isCheckedOrNot(IDREGEXP))
			{
				enableFindDlgItem(IDREDOTMATCHNL);
			}
			else
			{
				enableFindDlgItem(IDREDOTMATCHNL, false);
			}
			enableProjectCheckmarks();
			return TRUE;
		}

		case NPPM_MODELESSDIALOG :
			return ::SendMessage(_hParent, NPPM_MODELESSDIALOG, wParam, lParam);

		case WM_COMMAND :
		{
			bool isMacroRecording = (static_cast<MacroStatus>(::SendMessage(_hParent, NPPM_GETCURRENTMACROSTATUS,0,0)) == MacroStatus::RecordInProgress);
			NppParameters& nppParamInst = NppParameters::getInstance();
			FindHistory & findHistory = nppParamInst.getFindHistory();
			switch (LOWORD(wParam))
			{
//Single actions
				case IDC_2_BUTTONS_MODE:
				{
					bool is2ButtonsMode = isCheckedOrNot(IDC_2_BUTTONS_MODE);
					findHistory._isSearch2ButtonsMode = is2ButtonsMode;

					showFindDlgItem(IDC_FINDPREV, is2ButtonsMode);
					showFindDlgItem(IDC_FINDNEXT, is2ButtonsMode);
					showFindDlgItem(IDOK, !is2ButtonsMode);
				}
				break;

				case IDCANCEL:
					(*_ppEditView)->execute(SCI_CALLTIPCANCEL);
					setStatusbarMessage(generic_string(), FSNoMessage);
					display(false);
					break;

				case IDC_FINDPREV:
				case IDC_FINDNEXT:
				case IDOK : // Find Next : only for FIND_DLG and REPLACE_DLG
				{
					setStatusbarMessage(generic_string(), FSNoMessage);

					HWND hFindCombo = ::GetDlgItem(_hSelf, IDFINDWHAT);
					_options._str2Search = getTextFromCombo(hFindCombo);
					updateCombo(IDFINDWHAT);

					nppParamInst._isFindReplacing = true;

					bool direction_bak = _options._whichDirection;

					if (LOWORD(wParam) == IDC_FINDPREV)
					{
						_options._whichDirection = DIR_UP;
					}
					else if (LOWORD(wParam) == IDC_FINDNEXT)
					{
						_options._whichDirection = DIR_DOWN;
					}
					else // IDOK
					{
						// if shift-key is pressed, revert search direction
						// if shift-key is not pressed, use the normal setting
						SHORT shift = GetKeyState(VK_SHIFT);
						if (shift & SHIFTED)
						{
							_options._whichDirection = !_options._whichDirection;
						}
					}

					if ((_options._whichDirection == DIR_UP) && (_options._searchType == FindRegex) && !nppParamInst.regexBackward4PowerUser())
					{
						// this can only happen when shift-key was pressed
						// regex upward search is disabled
						// turn user action into a no-action step
					}
					else
					{
						if (isMacroRecording)
							saveInMacro(IDOK, FR_OP_FIND);

						FindStatus findStatus = FSFound;
						processFindNext(_options._str2Search.c_str(), _env, &findStatus);

						NativeLangSpeaker *pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();
						if (findStatus == FSEndReached)
						{
							generic_string msg = pNativeSpeaker->getLocalizedStrFromID("find-status-end-reached", TEXT("Find: Found the 1st occurrence from the top. The end of the document has been reached."));
							setStatusbarMessage(msg, FSEndReached);
						}
						else if (findStatus == FSTopReached)
						{
							generic_string msg = pNativeSpeaker->getLocalizedStrFromID("find-status-top-reached", TEXT("Find: Found the 1st occurrence from the bottom. The beginning of the document has been reached."));
							setStatusbarMessage(msg, FSTopReached);
						}
					}

					// restore search direction which may have been overwritten because shift-key was pressed
					_options._whichDirection = direction_bak;

					nppParamInst._isFindReplacing = false;
				}
				return TRUE;
					
				case IDC_SWAP_FIND_REPLACE:
				{
					if (_swapButtonStatus != swap)
					{
						_swapButtonStatus = swap;
						::SetWindowTextW(_hSwapButton, L"⇅");
						SendMessage(_hSwapButton, WM_SETFONT, (WPARAM)_hLargerBolderFont, MAKELPARAM(true, 0));
					}
					::SendMessage(_hSelf, WM_COMMAND, IDD_FINDREPLACE_SWAP_BUTTON, 0);
					return TRUE;
				}

				case IDC_COPY_FIND2REPLACE:
				{
					if (_swapButtonStatus != down)
					{
						_swapButtonStatus = down;
						::SetWindowTextW(_hSwapButton, L"⤵");
						SendMessage(_hSwapButton, WM_SETFONT, (WPARAM)_hLargerBolderFont, MAKELPARAM(true, 0));
					}
					::SendMessage(_hSelf, WM_COMMAND, IDD_FINDREPLACE_SWAP_BUTTON, 0);
					return TRUE;
				}

				case IDC_COPY_REPLACE2FIND:
				{
					if (_swapButtonStatus != up)
					{
						_swapButtonStatus = up;
						::SetWindowTextW(_hSwapButton, L"⤴");
						SendMessage(_hSwapButton, WM_SETFONT, (WPARAM)_hLargerBolderFont, MAKELPARAM(true, 0));
					}
					::SendMessage(_hSelf, WM_COMMAND, IDD_FINDREPLACE_SWAP_BUTTON, 0);
					return TRUE;
				}

				case IDD_FINDREPLACE_SWAP_BUTTON:
				{
					HWND hFindWhat = ::GetDlgItem(_hSelf, IDFINDWHAT);
					generic_string findWhatText = getTextFromCombo(hFindWhat);
					HWND hPlaceWith = ::GetDlgItem(_hSelf, IDREPLACEWITH);
					generic_string replaceWithText = getTextFromCombo(hPlaceWith);

					
					if (_swapButtonStatus == swap)
					{
						if ((!findWhatText.empty() || !replaceWithText.empty()) && (findWhatText != replaceWithText))
						{
							::SendMessage(hFindWhat, WM_SETTEXT, 0, reinterpret_cast<LPARAM>(replaceWithText.c_str()));
							::SendMessage(hPlaceWith, WM_SETTEXT, 0, reinterpret_cast<LPARAM>(findWhatText.c_str()));
						}
					}
					else if (_swapButtonStatus == down)
					{
						if (!findWhatText.empty() && (findWhatText != replaceWithText))
						{
							::SendMessage(hPlaceWith, WM_SETTEXT, 0, reinterpret_cast<LPARAM>(findWhatText.c_str()));
						}
					}
					else // if (_swapButtonStatus == up)
					{
						if (!replaceWithText.empty() && (findWhatText != replaceWithText))
						{
							::SendMessage(hFindWhat, WM_SETTEXT, 0, reinterpret_cast<LPARAM>(replaceWithText.c_str()));
						}
					}
				}
				return TRUE;

				case IDM_SEARCH_FIND:
					if (_currentStatus == FIND_DLG)
						goToCenter();
					else
						enableReplaceFunc(false);
					return TRUE;

				case IDM_SEARCH_REPLACE:
					if (_currentStatus == REPLACE_DLG)
						goToCenter();
					else
						enableReplaceFunc(true);
					return TRUE;

				case IDM_SEARCH_FINDINFILES:
					if (_currentStatus == FINDINFILES_DLG)
						goToCenter();
					else
						enableFindInFilesFunc();
					return TRUE;

				case IDM_SEARCH_MARK:
					if (_currentStatus == MARK_DLG)
						goToCenter();
					else
						enableMarkFunc();
					return TRUE;

				case IDREPLACE:
				{
					std::lock_guard<std::mutex> lock(findOps_mutex);

					if (_currentStatus == REPLACE_DLG)
					{
						setStatusbarMessage(TEXT(""), FSNoMessage);
						HWND hFindCombo = ::GetDlgItem(_hSelf, IDFINDWHAT);
						HWND hReplaceCombo = ::GetDlgItem(_hSelf, IDREPLACEWITH);
						_options._str2Search = getTextFromCombo(hFindCombo);
						_options._str4Replace = getTextFromCombo(hReplaceCombo);
						updateCombos();

						nppParamInst._isFindReplacing = true;
						if (isMacroRecording) saveInMacro(wParam, FR_OP_REPLACE);
						processReplace(_options._str2Search.c_str(), _options._str4Replace.c_str());
						nppParamInst._isFindReplacing = false;
					}
				}
				return TRUE;
//Process actions
				case IDC_FINDALL_OPENEDFILES :
				{
					if (_currentStatus == FIND_DLG)
					{
						setStatusbarMessage(TEXT(""), FSNoMessage);
						HWND hFindCombo = ::GetDlgItem(_hSelf, IDFINDWHAT);
						combo2ExtendedMode(IDFINDWHAT);
						_options._str2Search = getTextFromCombo(hFindCombo);
						updateCombo(IDFINDWHAT);

						nppParamInst._isFindReplacing = true;
						if (isMacroRecording) saveInMacro(wParam, FR_OP_FIND + FR_OP_GLOBAL);
						findAllIn(ALL_OPEN_DOCS);
						nppParamInst._isFindReplacing = false;
					}
				}
				return TRUE;

				case IDC_FINDALL_CURRENTFILE :
				{
					setStatusbarMessage(TEXT(""), FSNoMessage);
					HWND hFindCombo = ::GetDlgItem(_hSelf, IDFINDWHAT);
					combo2ExtendedMode(IDFINDWHAT);
					_options._str2Search = getTextFromCombo(hFindCombo);
					updateCombo(IDFINDWHAT);

					nppParamInst._isFindReplacing = true;
					if (isMacroRecording) saveInMacro(wParam, FR_OP_FIND + FR_OP_GLOBAL);
					findAllIn(_options._isInSelection ? CURR_DOC_SELECTION : CURRENT_DOC);
					nppParamInst._isFindReplacing = false;
				}
				return TRUE;

				case IDD_FINDINFILES_FIND_BUTTON:
				{
					setStatusbarMessage(TEXT(""), FSNoMessage);
					constexpr int filterSize = 512;
					TCHAR filters[filterSize + 1] = { '\0' };
					filters[filterSize] = '\0';
					TCHAR directory[MAX_PATH]{};
					::GetDlgItemText(_hSelf, IDD_FINDINFILES_FILTERS_COMBO, filters, filterSize);
					addText2Combo(filters, ::GetDlgItem(_hSelf, IDD_FINDINFILES_FILTERS_COMBO));
					_options._filters = filters;

					HWND hFindCombo = ::GetDlgItem(_hSelf, IDFINDWHAT);
					combo2ExtendedMode(IDFINDWHAT);
					_options._str2Search = getTextFromCombo(hFindCombo);
					updateCombo(IDFINDWHAT);

					if (_currentStatus == FINDINFILES_DLG)
					{
						::GetDlgItemText(_hSelf, IDD_FINDINFILES_DIR_COMBO, directory, MAX_PATH);
						_options._directory = directory;
						trim(_options._directory);
						if (!_options._directory.empty())
						{
							addText2Combo(_options._directory.c_str(), ::GetDlgItem(_hSelf, IDD_FINDINFILES_DIR_COMBO));
							if (_options._directory.back() != L'\\')
							{
								_options._directory += TEXT("\\");
							}
							nppParamInst._isFindReplacing = true;
							if (isMacroRecording) saveInMacro(wParam, FR_OP_FIND + FR_OP_FIF);
							findAllIn(FILES_IN_DIR);
							nppParamInst._isFindReplacing = false;
						}
					}
					else if (_currentStatus == FINDINPROJECTS_DLG)
					{
						if (_options._isProjectPanel_1 || _options._isProjectPanel_2 || _options._isProjectPanel_3)
						{
							nppParamInst._isFindReplacing = true;
							if (isMacroRecording) saveInMacro(IDD_FINDINFILES_FINDINPROJECTS, FR_OP_FIND + FR_OP_FIP);
							findAllIn(FILES_IN_PROJECTS);
							nppParamInst._isFindReplacing = false;
						}
					}
				}
				return TRUE;

				case IDD_FINDINFILES_REPLACEINFILES:
				{
					std::lock_guard<std::mutex> lock(findOps_mutex);

					setStatusbarMessage(TEXT(""), FSNoMessage);
					constexpr int filterSize = 512;
					TCHAR filters[filterSize]{};
					TCHAR directory[MAX_PATH]{};
					::GetDlgItemText(_hSelf, IDD_FINDINFILES_FILTERS_COMBO, filters, filterSize);
					addText2Combo(filters, ::GetDlgItem(_hSelf, IDD_FINDINFILES_FILTERS_COMBO));
					_options._filters = filters;

					::GetDlgItemText(_hSelf, IDD_FINDINFILES_DIR_COMBO, directory, MAX_PATH);
					_options._directory = directory;
					trim(_options._directory);
					if (!_options._directory.empty())
					{
						if (replaceInFilesConfirmCheck(_options._directory, _options._filters))
						{
							addText2Combo(_options._directory.c_str(), ::GetDlgItem(_hSelf, IDD_FINDINFILES_DIR_COMBO));
							if (_options._directory.back() != L'\\')
							{
								_options._directory += TEXT("\\");
							}

							HWND hFindCombo = ::GetDlgItem(_hSelf, IDFINDWHAT);
							_options._str2Search = getTextFromCombo(hFindCombo);
							HWND hReplaceCombo = ::GetDlgItem(_hSelf, IDREPLACEWITH);
							_options._str4Replace = getTextFromCombo(hReplaceCombo);
							updateCombo(IDFINDWHAT);
							updateCombo(IDREPLACEWITH);

							nppParamInst._isFindReplacing = true;
							if (isMacroRecording) saveInMacro(wParam, FR_OP_REPLACE + FR_OP_FIF);
							::SendMessage(_hParent, WM_REPLACEINFILES, 0, 0);
							nppParamInst._isFindReplacing = false;
						}
					}
				}
				return TRUE;

				case IDD_FINDINFILES_REPLACEINPROJECTS:
				{
					std::lock_guard<std::mutex> lock(findOps_mutex);

					setStatusbarMessage(TEXT(""), FSNoMessage);
					constexpr int filterSize = 512;
					TCHAR filters[filterSize]{};
					::GetDlgItemText(_hSelf, IDD_FINDINFILES_FILTERS_COMBO, filters, filterSize);
					addText2Combo(filters, ::GetDlgItem(_hSelf, IDD_FINDINFILES_FILTERS_COMBO));
					_options._filters = filters;
					if (replaceInProjectsConfirmCheck())
					{
						HWND hFindCombo = ::GetDlgItem(_hSelf, IDFINDWHAT);
						_options._str2Search = getTextFromCombo(hFindCombo);
						HWND hReplaceCombo = ::GetDlgItem(_hSelf, IDREPLACEWITH);
						_options._str4Replace = getTextFromCombo(hReplaceCombo);
						updateCombo(IDFINDWHAT);
						updateCombo(IDREPLACEWITH);

						nppParamInst._isFindReplacing = true;
						if (isMacroRecording) saveInMacro(wParam, FR_OP_REPLACE + FR_OP_FIP);
						::SendMessage(_hParent, WM_REPLACEINPROJECTS, 0, 0);
						nppParamInst._isFindReplacing = false;
					}
				}
				return TRUE;

				case IDC_REPLACE_OPENEDFILES :
				{
					std::lock_guard<std::mutex> lock(findOps_mutex);

					if (_currentStatus == REPLACE_DLG)
					{
						NppParameters& nppParam = NppParameters::getInstance();
						const NppGUI& nppGui = nppParam.getNppGUI();
						if (!nppGui._confirmReplaceInAllOpenDocs || replaceInOpenDocsConfirmCheck())
						{
							setStatusbarMessage(TEXT(""), FSNoMessage);
							HWND hFindCombo = ::GetDlgItem(_hSelf, IDFINDWHAT);
							_options._str2Search = getTextFromCombo(hFindCombo);
							HWND hReplaceCombo = ::GetDlgItem(_hSelf, IDREPLACEWITH);
							_options._str4Replace = getTextFromCombo(hReplaceCombo);
							updateCombos();

							nppParamInst._isFindReplacing = true;
							if (isMacroRecording) saveInMacro(wParam, FR_OP_REPLACE + FR_OP_GLOBAL);
							replaceAllInOpenedDocs();
							nppParamInst._isFindReplacing = false;
						}
					}
				}
				return TRUE;

				case IDREPLACEALL :
				{
					std::lock_guard<std::mutex> lock(findOps_mutex);

					if (_currentStatus == REPLACE_DLG)
					{
						setStatusbarMessage(TEXT(""), FSNoMessage);
						if ((*_ppEditView)->getCurrentBuffer()->isReadOnly())
						{
							NativeLangSpeaker *pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();
							generic_string msg = pNativeSpeaker->getLocalizedStrFromID("find-status-replace-readonly", TEXT("Replace: Cannot replace text. The current document is read only."));
							setStatusbarMessage(msg, FSNotFound);
							return TRUE;
						}

						HWND hFindCombo = ::GetDlgItem(_hSelf, IDFINDWHAT);
						_options._str2Search = getTextFromCombo(hFindCombo);
						HWND hReplaceCombo = ::GetDlgItem(_hSelf, IDREPLACEWITH);
						_options._str4Replace = getTextFromCombo(hReplaceCombo);
						updateCombos();

						nppParamInst._isFindReplacing = true;
						if (isMacroRecording) saveInMacro(wParam, FR_OP_REPLACE);
						(*_ppEditView)->execute(SCI_BEGINUNDOACTION);
						int nbReplaced = processAll(ProcessReplaceAll, &_options);
						(*_ppEditView)->execute(SCI_ENDUNDOACTION);
						nppParamInst._isFindReplacing = false;

						
						NativeLangSpeaker *pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();
						if (nbReplaced == FIND_INVALID_REGULAR_EXPRESSION)
						{
							setStatusbarMessageWithRegExprErr(*_ppEditView);
						}
						else
						{
							wstring result;
							if (nbReplaced == 1)
							{
								result = pNativeSpeaker->getLocalizedStrFromID("find-status-replaceall-1-replaced", TEXT("Replace All: 1 occurrence was replaced"));
							}
							else
							{
								result = pNativeSpeaker->getLocalizedStrFromID("find-status-replaceall-nb-replaced", TEXT("Replace All: $INT_REPLACE$ occurrences were replaced"));
								result = stringReplace(result, TEXT("$INT_REPLACE$"), std::to_wstring(nbReplaced));
							}
							result += TEXT(" ");
							result += getScopeInfoForStatusBar(&_options);

							setStatusbarMessage(result, FSMessage);
						}
						getFocus();
					}
				}
				return TRUE;

				case IDCCOUNTALL :
				{
					if (_currentStatus == FIND_DLG)
					{
						setStatusbarMessage(TEXT(""), FSNoMessage);
						HWND hFindCombo = ::GetDlgItem(_hSelf, IDFINDWHAT);
						updateCombo(IDFINDWHAT);
						_options._str2Search = getTextFromCombo(hFindCombo);

						int nbCounted = processAll(ProcessCountAll, &_options);

						NativeLangSpeaker *pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();
						if (nbCounted == FIND_INVALID_REGULAR_EXPRESSION)
						{
							setStatusbarMessageWithRegExprErr(*_ppEditView);
						}
						else
						{
							wstring result;
							if (nbCounted == 1)
							{
								result = pNativeSpeaker->getLocalizedStrFromID("find-status-count-1-match", TEXT("Count: 1 match"));
							}
							else //if (nbCounted == 0 || nbCounted > 1)
							{
								result = pNativeSpeaker->getLocalizedStrFromID("find-status-count-nb-matches", TEXT("Count: $INT_REPLACE$ matches"));
								result = stringReplace(result, TEXT("$INT_REPLACE$"), std::to_wstring(nbCounted));
							}
							result += TEXT(" ");
							result += getScopeInfoForStatusBar(&_options);
							
							setStatusbarMessage(result, FSMessage);
						}

						if (isMacroRecording)
							saveInMacro(wParam, FR_OP_FIND);
						
						getFocus();
					}
				}
				return TRUE;

				case IDCMARKALL :
				{
					if (_currentStatus == MARK_DLG)
					{
						setStatusbarMessage(TEXT(""), FSNoMessage);
						HWND hFindCombo = ::GetDlgItem(_hSelf, IDFINDWHAT);
						_options._str2Search = getTextFromCombo(hFindCombo);
						updateCombo(IDFINDWHAT);

						if (isMacroRecording) saveInMacro(wParam, FR_OP_FIND);
						nppParamInst._isFindReplacing = true;
						int nbMarked = processAll(ProcessMarkAll, &_options);
						nppParamInst._isFindReplacing = false;

						
						NativeLangSpeaker *pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();
						if (nbMarked == FIND_INVALID_REGULAR_EXPRESSION)
						{
							setStatusbarMessageWithRegExprErr(*_ppEditView);
						}
						else
						{
							std::wstring result;
							if (nbMarked == 1)
							{
								result = pNativeSpeaker->getLocalizedStrFromID("find-status-mark-1-match", TEXT("Mark: 1 match"));
							}
							else //if (nbMarked == 0 || nbMarked > 1)
							{
								result = pNativeSpeaker->getLocalizedStrFromID("find-status-mark-nb-matches", TEXT("Mark: $INT_REPLACE$ matches"));
								result = stringReplace(result, TEXT("$INT_REPLACE$"), std::to_wstring(nbMarked));
							}
							result += TEXT(" ");
							result += getScopeInfoForStatusBar(&_options);
							
							setStatusbarMessage(result, FSMessage);
						}
						
						getFocus();
					}
				}
				return TRUE;

				case IDC_CLEAR_ALL :
				{
					if (_currentStatus == MARK_DLG)
					{
						if (isMacroRecording) saveInMacro(wParam, FR_OP_FIND);
						clearMarks(_options);
					}
				}
				return TRUE;

				case IDC_COPY_MARKED_TEXT:
				{
					::SendMessage(_hParent, WM_COMMAND, IDM_SEARCH_MARKEDTOCLIP, 0);
				}
				return TRUE;

//Option actions
				case IDREDOTMATCHNL:
					findHistory._dotMatchesNewline = _options._dotMatchesNewline = isCheckedOrNot(IDREDOTMATCHNL);
					return TRUE;

				case IDWHOLEWORD :
					findHistory._isMatchWord = _options._isWholeWord = isCheckedOrNot(IDWHOLEWORD);
					return TRUE;

				case IDMATCHCASE :
					findHistory._isMatchCase = _options._isMatchCase = isCheckedOrNot(IDMATCHCASE);
					return TRUE;

				case IDNORMAL:
				case IDEXTENDED:
				case IDREGEXP :
				{
					if (isCheckedOrNot(IDREGEXP))
					{
						_options._searchType = FindRegex;
						findHistory._searchMode = FindHistory::regExpr;
						enableFindDlgItem(IDREDOTMATCHNL);
					}
					else if (isCheckedOrNot(IDEXTENDED))
					{
						_options._searchType = FindExtended;
						findHistory._searchMode = FindHistory::extended;
						enableFindDlgItem(IDREDOTMATCHNL, false);
					}
					else
					{
						_options._searchType = FindNormal;
						findHistory._searchMode = FindHistory::normal;
						enableFindDlgItem(IDREDOTMATCHNL, false);
					}

					bool isRegex = (_options._searchType == FindRegex);
					if (isRegex)
					{
						//regex doesn't allow whole word
						_options._isWholeWord = false;
						::SendDlgItemMessage(_hSelf, IDWHOLEWORD, BM_SETCHECK, _options._isWholeWord?BST_CHECKED:BST_UNCHECKED, 0);

						//regex upward search is disabled
						if (!nppParamInst.regexBackward4PowerUser())
						{
							::SendDlgItemMessage(_hSelf, IDC_BACKWARDDIRECTION, BM_SETCHECK, BST_UNCHECKED, 0);
							_options._whichDirection = DIR_DOWN;
						}
					}

					enableFindDlgItem(IDWHOLEWORD, !isRegex);

					// regex upward search is disabled
					bool doEnable = true;
					if (isRegex && !nppParamInst.regexBackward4PowerUser())
					{
						doEnable = false;
					}
					enableFindDlgItem(IDC_BACKWARDDIRECTION, doEnable);
					enableFindDlgItem(IDC_FINDPREV, doEnable);

					return TRUE;
				}

				case IDWRAP :
					findHistory._isWrap = _options._isWrapAround = isCheckedOrNot(IDWRAP);
					return TRUE;

				case IDC_BACKWARDDIRECTION:
					_options._whichDirection = isCheckedOrNot(IDC_BACKWARDDIRECTION) ? DIR_UP : DIR_DOWN;
					findHistory._isDirectionDown = _options._whichDirection == DIR_DOWN;
					return TRUE;

				case IDC_PURGE_CHECK :
				{
					if (_currentStatus == MARK_DLG)
						findHistory._isPurge = _options._doPurge = isCheckedOrNot(IDC_PURGE_CHECK);
				}
				return TRUE;

				case IDC_MARKLINE_CHECK :
				{
					if (_currentStatus == MARK_DLG)
						findHistory._isBookmarkLine = _options._doMarkLine = isCheckedOrNot(IDC_MARKLINE_CHECK);
				}
				return TRUE;

				case IDC_IN_SELECTION_CHECK :
				{
					if ((_currentStatus == FIND_DLG) || (_currentStatus == REPLACE_DLG) || (_currentStatus == MARK_DLG))
					{
						_options._isInSelection = ::IsWindowEnabled(::GetDlgItem(_hSelf, IDC_IN_SELECTION_CHECK)) &&
							isCheckedOrNot(IDC_IN_SELECTION_CHECK);
					}
				}
				return TRUE;

				case IDC_TRANSPARENT_CHECK :
				{
					bool isChecked = isCheckedOrNot(IDC_TRANSPARENT_CHECK);

					enableFindDlgItem(IDC_TRANSPARENT_LOSSFOCUS_RADIO, isChecked);
					enableFindDlgItem(IDC_TRANSPARENT_ALWAYS_RADIO, isChecked);
					enableFindDlgItem(IDC_PERCENTAGE_SLIDER, isChecked);

					if (isChecked)
					{
						::SendDlgItemMessage(_hSelf, IDC_TRANSPARENT_LOSSFOCUS_RADIO, BM_SETCHECK, BST_CHECKED, 0);
						findHistory._transparencyMode = FindHistory::onLossingFocus;
					}
					else
					{
						::SendDlgItemMessage(_hSelf, IDC_TRANSPARENT_LOSSFOCUS_RADIO, BM_SETCHECK, BST_UNCHECKED, 0);
						::SendDlgItemMessage(_hSelf, IDC_TRANSPARENT_ALWAYS_RADIO, BM_SETCHECK, BST_UNCHECKED, 0);
						(NppParameters::getInstance()).removeTransparent(_hSelf);
						findHistory._transparencyMode = FindHistory::none;
					}

					return TRUE;
				}

				case IDC_TRANSPARENT_ALWAYS_RADIO :
				{
					int percent = static_cast<int32_t>(::SendDlgItemMessage(_hSelf, IDC_PERCENTAGE_SLIDER, TBM_GETPOS, 0, 0));
					(NppParameters::getInstance()).SetTransparent(_hSelf, percent);
					findHistory._transparencyMode = FindHistory::persistant;
				}
				return TRUE;

				case IDC_TRANSPARENT_LOSSFOCUS_RADIO :
				{
					(NppParameters::getInstance()).removeTransparent(_hSelf);
					findHistory._transparencyMode = FindHistory::onLossingFocus;
				}
				return TRUE;

				//
				// Find in Files
				//
				case IDD_FINDINFILES_RECURSIVE_CHECK :
				{
					if (_currentStatus == FINDINFILES_DLG)
						findHistory._isFifRecuisive = _options._isRecursive = isCheckedOrNot(IDD_FINDINFILES_RECURSIVE_CHECK);

				}
				return TRUE;

				case IDD_FINDINFILES_INHIDDENDIR_CHECK :
				{
					if (_currentStatus == FINDINFILES_DLG)
						findHistory._isFifInHiddenFolder = _options._isInHiddenDir = isCheckedOrNot(IDD_FINDINFILES_INHIDDENDIR_CHECK);
				}
				return TRUE;

				case IDD_FINDINFILES_PROJECT1_CHECK:
				case IDD_FINDINFILES_PROJECT2_CHECK:
				case IDD_FINDINFILES_PROJECT3_CHECK:
				{
					if (_currentStatus == FINDINPROJECTS_DLG)
					{
						switch (LOWORD(wParam))
						{
							case IDD_FINDINFILES_PROJECT1_CHECK:
								findHistory._isFifProjectPanel_1 = _options._isProjectPanel_1 = isCheckedOrNot(IDD_FINDINFILES_PROJECT1_CHECK);
								break;
							case IDD_FINDINFILES_PROJECT2_CHECK:
								findHistory._isFifProjectPanel_2 = _options._isProjectPanel_2 = isCheckedOrNot(IDD_FINDINFILES_PROJECT2_CHECK);
								break;
							case IDD_FINDINFILES_PROJECT3_CHECK:
								findHistory._isFifProjectPanel_3 = _options._isProjectPanel_3 = isCheckedOrNot(IDD_FINDINFILES_PROJECT3_CHECK);
								break;
						}
						bool enable = _options._isProjectPanel_1 || _options._isProjectPanel_2 || _options._isProjectPanel_3;
						enableFindDlgItem(IDD_FINDINFILES_FIND_BUTTON, enable);
						enableFindDlgItem(IDD_FINDINFILES_REPLACEINPROJECTS, enable);
					}
				}
				return TRUE;

				case IDD_FINDINFILES_FOLDERFOLLOWSDOC_CHECK :
				{
					if (_currentStatus == FINDINFILES_DLG)
						findHistory._isFolderFollowDoc = isCheckedOrNot(IDD_FINDINFILES_FOLDERFOLLOWSDOC_CHECK);

					if (findHistory._isFolderFollowDoc)
					{
						// Working directory depends on "Default Directory" preferences.
						// It might be set to an absolute path value.
						// So try to get the current buffer's path first.
						generic_string currPath;
						const Buffer* buf = (*_ppEditView)->getCurrentBuffer();
						if (!(buf->getStatus() & (DOC_UNNAMED | DOC_DELETED)))
						{
							currPath = buf->getFullPathName();
							PathRemoveFileSpec(currPath);
						}
						if (currPath.empty() || !PathIsDirectory(currPath.c_str()))
							currPath = NppParameters::getInstance().getWorkingDir();
						::SetDlgItemText(_hSelf, IDD_FINDINFILES_DIR_COMBO, currPath.c_str());
					}

				}
				return TRUE;

				case IDD_FINDINFILES_BROWSE_BUTTON :
				{
					if (_currentStatus == FINDINFILES_DLG)
					{
						NativeLangSpeaker* pNativeSpeaker = NppParameters::getInstance().getNativeLangSpeaker();
						const generic_string title = pNativeSpeaker->getLocalizedStrFromID("find-in-files-select-folder", TEXT("Select a folder to search from"));
						folderBrowser(_hSelf, title, IDD_FINDINFILES_DIR_COMBO, _options._directory.c_str());
					}
				}
				return TRUE;

				case IDD_RESIZE_TOGGLE_BUTTON:
				{
					RECT rc = { 0, 0, 0, 0};
					getWindowRect(rc);
					int w = rc.right - rc.left;
					POINT p{};
					p.x = rc.left;
					p.y = rc.top;
					bool& isLessModeOn = NppParameters::getInstance().getNppGUI()._findWindowLessMode;
					isLessModeOn = !isLessModeOn;
					long dlgH = isLessModeOn ? _lesssModeHeight : _initialWindowRect.bottom;
					RECT& buttonRc = isLessModeOn ? _uncollapseButtonPos : _collapseButtonPos;

					// For unknown reason, the original default width doesn't make the status bar moveed
					// Here we use a dirty workaround: increase 1 pixel so WM_SIZE message will be triggered
					if (w == _initialWindowRect.right)
						w += 1;

					::MoveWindow(_hSelf, p.x, p.y, w, dlgH, FALSE); // WM_SIZE message to call resizeDialogElements - status bar will be reposition correctly.
					::MoveWindow(::GetDlgItem(_hSelf, IDD_RESIZE_TOGGLE_BUTTON), buttonRc.left + _deltaWidth, buttonRc.top, buttonRc.right, buttonRc.bottom, TRUE);

					// Reposition the status bar
					const UINT flags = SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOCOPYBITS | SWP_DRAWFRAME;
					::GetClientRect(_statusBar.getHSelf(), &rc);
					::SetWindowPos(_statusBar.getHSelf(), 0, 0, 0, rc.right + _deltaWidth, rc.bottom, flags);

					DIALOG_TYPE dlgT = getCurrentStatus();

					hideOrShowCtrl4reduceOrNormalMode(dlgT);

					::SetDlgItemText(_hSelf, IDD_RESIZE_TOGGLE_BUTTON, isLessModeOn ? L"˅" : L"˄");
					
					redraw();
				}
				return TRUE;

				default :
					break;
			}
			break;
		}
	}
	return FALSE;
}

// return value :
// true  : the text2find is found
// false : the text2find is not found

bool FindReplaceDlg::processFindNext(const TCHAR *txt2find, const FindOption *options, FindStatus *oFindStatus, FindNextType findNextType /* = FINDNEXTTYPE_FINDNEXT */)
{
	if (oFindStatus)
		*oFindStatus = FSFound;

	if (!txt2find || !txt2find[0])
		return false;

	const FindOption *pOptions = options?options:_env;

	(*_ppEditView)->execute(SCI_CALLTIPCANCEL);

	int stringSizeFind = lstrlen(txt2find);
	TCHAR *pText = new TCHAR[stringSizeFind + 1];
	wcscpy_s(pText, stringSizeFind + 1, txt2find);

	if (pOptions->_searchType == FindExtended)
	{
		stringSizeFind = Searching::convertExtendedToString(txt2find, pText, stringSizeFind);
	}

	intptr_t docLength = (*_ppEditView)->execute(SCI_GETLENGTH);
	Sci_CharacterRangeFull cr = (*_ppEditView)->getSelection();


	//The search "zone" is relative to the selection, so search happens 'outside'
	intptr_t startPosition = cr.cpMax;
	intptr_t endPosition = docLength;


	if (pOptions->_whichDirection == DIR_UP)
	{
		//When searching upwards, start is the lower part, end the upper, for backwards search
		startPosition = cr.cpMax - 1;
		endPosition = 0;
	}

	if (FirstIncremental==pOptions->_incrementalType)
	{
		// the text to find is modified so use the current position
		startPosition = cr.cpMin;
		endPosition = docLength;

		if (pOptions->_whichDirection == DIR_UP)
		{
			//When searching upwards, start is the lower part, end the upper, for backwards search
			startPosition = cr.cpMax;
			endPosition = 0;
		}
	}
	else if (NextIncremental==pOptions->_incrementalType)
	{
		// text to find is not modified, so use current position +1
		startPosition = cr.cpMin + 1;
		endPosition = docLength;

		if (pOptions->_whichDirection == DIR_UP)
		{
			//When searching upwards, start is the lower part, end the upper, for backwards search
			startPosition = cr.cpMax - 1;
			endPosition = 0;
		}
	}

	int flags = Searching::buildSearchFlags(pOptions);
	switch (findNextType)
	{
		case FINDNEXTTYPE_FINDNEXT:
			flags |= SCFIND_REGEXP_EMPTYMATCH_ALL | SCFIND_REGEXP_SKIPCRLFASONE;
		break;

		case FINDNEXTTYPE_REPLACENEXT:
			flags |= SCFIND_REGEXP_EMPTYMATCH_NOTAFTERMATCH | SCFIND_REGEXP_SKIPCRLFASONE;
			break;

		case FINDNEXTTYPE_FINDNEXTFORREPLACE:
			flags |= SCFIND_REGEXP_EMPTYMATCH_ALL | SCFIND_REGEXP_EMPTYMATCH_ALLOWATSTART | SCFIND_REGEXP_SKIPCRLFASONE;
			break;
	}

	intptr_t start, end;
	intptr_t posFind;

	// Never allow a zero length match in the middle of a line end marker
	if ((*_ppEditView)->execute(SCI_GETCHARAT, startPosition - 1) == '\r'
		&& (*_ppEditView)->execute(SCI_GETCHARAT, startPosition) == '\n')
	{
		flags = (flags & ~SCFIND_REGEXP_EMPTYMATCH_MASK) | SCFIND_REGEXP_EMPTYMATCH_NONE;
	}

	(*_ppEditView)->execute(SCI_SETSEARCHFLAGS, flags);


	posFind = (*_ppEditView)->searchInTarget(pText, stringSizeFind, startPosition, endPosition);
	if (posFind == -1) //no match found in target, check if a new target should be used
	{
		if (pOptions->_isWrapAround)
		{
			//when wrapping, use the rest of the document (entire document is usable)
			if (pOptions->_whichDirection == DIR_DOWN)
			{
				startPosition = 0;
				endPosition = docLength;
				if (oFindStatus)
					*oFindStatus = FSEndReached;
			}
			else
			{
				startPosition = docLength;
				endPosition = 0;
				if (oFindStatus)
					*oFindStatus = FSTopReached;
			}

			//new target, search again
			posFind = (*_ppEditView)->searchInTarget(pText, stringSizeFind, startPosition, endPosition);
		}

		if (posFind == -1)
		{ // not found
			if (oFindStatus)
				*oFindStatus = FSNotFound;
			//failed, or failed twice with wrap
			if (NotIncremental == pOptions->_incrementalType) //incremental search doesnt trigger messages
			{
				generic_string newTxt2find = stringReplace(txt2find, TEXT("&"), TEXT("&&"));
				NativeLangSpeaker *pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();
				generic_string msg = pNativeSpeaker->getLocalizedStrFromID("find-status-cannot-find", TEXT("Find: Can't find the text \"$STR_REPLACE$\""));
				msg = stringReplace(msg, TEXT("$STR_REPLACE$"), newTxt2find);
				setStatusbarMessage(msg, FSNotFound);

				// if the dialog is not shown, pass the focus to his parent(ie. Notepad++)
				if (!::IsWindowVisible(_hSelf))
				{
					(*_ppEditView)->getFocus();
				}
				else
				{
					::SetFocus(::GetDlgItem(_hSelf, IDFINDWHAT));
				}
			}
			delete [] pText;
			return false;
		}
	}
	else if (posFind == FIND_INVALID_REGULAR_EXPRESSION)
	{ // error
		setStatusbarMessageWithRegExprErr(*_ppEditView);
		return false;
	}

	start =	posFind;
	end = (*_ppEditView)->execute(SCI_GETTARGETEND);

	setStatusbarMessage(TEXT(""), FSNoMessage);

	// to make sure the found result is visible:
	// prevent recording of absolute positioning commands issued in the process
	(*_ppEditView)->execute(SCI_STOPRECORD);
	Searching::displaySectionCentered(start, end, *_ppEditView, pOptions->_whichDirection == DIR_DOWN);
	// Show a calltip for a zero length match
	if (start == end)
	{
		NativeLangSpeaker* pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();
		generic_string msg = pNativeSpeaker->getLocalizedStrFromID("find-regex-zero-length-match", TEXT("zero length match"));
		msg = TEXT("^ ") + msg;
		(*_ppEditView)->showCallTip(start, msg.c_str());
	}
	if (static_cast<MacroStatus>(::SendMessage(_hParent, NPPM_GETCURRENTMACROSTATUS,0,0)) == MacroStatus::RecordInProgress)
		(*_ppEditView)->execute(SCI_STARTRECORD);

	delete [] pText;

	return true;
}

// return value :
// true  : the text is replaced, and find the next occurrence
// false : the text2find is not found, so the text is NOT replace
//      || the text is replaced, and do NOT find the next occurrence
bool FindReplaceDlg::processReplace(const TCHAR *txt2find, const TCHAR *txt2replace, const FindOption *options)
{
	bool moreMatches;

	if (!txt2find || !txt2find[0] || !txt2replace)
		return false;

	if ((*_ppEditView)->getCurrentBuffer()->isReadOnly())
	{
		NativeLangSpeaker *pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();
		generic_string msg = pNativeSpeaker->getLocalizedStrFromID("find-status-replace-readonly", TEXT("Replace: Cannot replace text. The current document is read only."));
		setStatusbarMessage(msg, FSNotFound);
		return false;
	}

	FindOption replaceOptions = options ? *options : *_env;
	replaceOptions._incrementalType = FirstIncremental;

	Sci_CharacterRangeFull currentSelection = (*_ppEditView)->getSelection();
	FindStatus status;
	moreMatches = processFindNext(txt2find, &replaceOptions, &status, FINDNEXTTYPE_FINDNEXTFORREPLACE);

	if (moreMatches)
	{
		Sci_CharacterRangeFull nextFind = (*_ppEditView)->getSelection();

		// If the next find is the same as the last, then perform the replacement
		if (nextFind.cpMin == currentSelection.cpMin && nextFind.cpMax == currentSelection.cpMax)
		{
			bool isRegExp = replaceOptions._searchType == FindRegex;

			intptr_t start = currentSelection.cpMin;
			intptr_t replacedLen = 0;
			if (isRegExp)
			{
				replacedLen = (*_ppEditView)->replaceTargetRegExMode(txt2replace);
			}
			else
			{
				if (replaceOptions._searchType == FindExtended)
				{
					int stringSizeReplace = lstrlen(txt2replace);
					TCHAR *pText2ReplaceExtended = new TCHAR[stringSizeReplace + 1];
					Searching::convertExtendedToString(txt2replace, pText2ReplaceExtended, stringSizeReplace);

					replacedLen = (*_ppEditView)->replaceTarget(pText2ReplaceExtended);

					delete[] pText2ReplaceExtended;
				}
				else
				{
					replacedLen = (*_ppEditView)->replaceTarget(txt2replace);
				}
			}
			(*_ppEditView)->execute(SCI_SETSEL, start + replacedLen, start + replacedLen);

			NativeLangSpeaker* pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();

			NppParameters& nppParam = NppParameters::getInstance();
			const NppGUI& nppGui = nppParam.getNppGUI();

			if (nppGui._replaceStopsWithoutFindingNext)
			{
				generic_string msg = pNativeSpeaker->getLocalizedStrFromID("find-status-replaced-without-continuing", TEXT("Replace: 1 occurrence was replaced."));
				setStatusbarMessage(msg, FSMessage);
			}
			else
			{
				// Do the next find
				moreMatches = processFindNext(txt2find, &replaceOptions, &status, FINDNEXTTYPE_REPLACENEXT);

				if (status == FSEndReached)
				{
					generic_string msg = pNativeSpeaker->getLocalizedStrFromID("find-status-replace-end-reached", TEXT("Replace: Replaced the 1st occurrence from the top. The end of document has been reached."));
					setStatusbarMessage(msg, FSEndReached);
				}
				else if (status == FSTopReached)
				{
					generic_string msg = pNativeSpeaker->getLocalizedStrFromID("find-status-replace-top-reached", TEXT("Replace: Replaced the 1st occurrence from the bottom. The begin of document has been reached."));
					setStatusbarMessage(msg, FSTopReached);
				}
				else
				{
					generic_string msg;
					if (moreMatches)
						msg = pNativeSpeaker->getLocalizedStrFromID("find-status-replaced-next-found", TEXT("Replace: 1 occurrence was replaced. The next occurrence found."));
					else
						msg = pNativeSpeaker->getLocalizedStrFromID("find-status-replaced-next-not-found", TEXT("Replace: 1 occurrence was replaced. No more occurrences were found."));

					setStatusbarMessage(msg, FSMessage);
				}
			}
		}
	}
	else
	{
		NativeLangSpeaker *pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();
		generic_string msg = pNativeSpeaker->getLocalizedStrFromID("find-status-replace-not-found", TEXT("Replace: no occurrence was found."));

		if (_statusbarTooltipMsg.empty()) // Tooltip message non-empty means there's a find problem - so we keep the message as it is and not erase it
			setStatusbarMessage(msg, FSNotFound);
	}

	return moreMatches;
}



int FindReplaceDlg::markAll(const TCHAR *txt2find, int styleID)
{
	const NppGUI& nppGUI = NppParameters::getInstance().getNppGUI();
	FindOption markAllOpt;

	markAllOpt._isMatchCase = nppGUI._markAllCaseSensitive;
	markAllOpt._isWholeWord = nppGUI._markAllWordOnly;
	markAllOpt._str2Search = txt2find;

	int nbFound = processAll(ProcessMarkAllExt, &markAllOpt, true, NULL, styleID);
	return nbFound;
}


int FindReplaceDlg::markAllInc(const FindOption *opt)
{
	int nbFound = processAll(ProcessMarkAll_IncSearch, opt,  true);
	return nbFound;
}

int FindReplaceDlg::processAll(ProcessOperation op, const FindOption *opt, bool isEntire, const FindersInfo *pFindersInfo, int colourStyleID)
{
	if (op == ProcessReplaceAll && (*_ppEditView)->getCurrentBuffer()->isReadOnly())
	{
		NativeLangSpeaker *pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();
		generic_string msg = pNativeSpeaker->getLocalizedStrFromID("find-status-replaceall-readonly", TEXT("Replace All: Cannot replace text. The current document is read only."));
		setStatusbarMessage(msg, FSNotFound);
		return 0;
	}

	const FindOption *pOptions = opt?opt:_env;
	const TCHAR *txt2find = pOptions->_str2Search.c_str();
	const TCHAR *txt2replace = pOptions->_str4Replace.c_str();

	Sci_CharacterRangeFull cr = (*_ppEditView)->getSelection();
	size_t docLength = (*_ppEditView)->execute(SCI_GETLENGTH);

	// Default :
	//        direction : down
	//        begin at : 0
	//        end at : end of doc
	size_t startPosition = 0;
	size_t endPosition = docLength;

	bool direction = pOptions->_whichDirection;

	//first try limiting scope by direction
	if (direction == DIR_UP)
	{
		startPosition = 0;
		endPosition = cr.cpMax;
	}
	else
	{
		startPosition = cr.cpMin;
		endPosition = docLength;
	}

	//then adjust scope if the full document needs to be changed
	if (op == ProcessCountAll && pOptions->_isInSelection)
	{
		startPosition = cr.cpMin;
		endPosition = cr.cpMax;
	}
	else if (pOptions->_isWrapAround || isEntire)	//entire document needs to be scanned
	{
		startPosition = 0;
		endPosition = docLength;
	}

	//then readjust scope if the selection override is active and allowed
	if ((pOptions->_isInSelection) && ((op == ProcessMarkAll) || ((op == ProcessReplaceAll || op == ProcessFindAll) && (!isEntire))))
		//if selection limiter and either mark all or replace all or find all w/o entire document override
	{
		startPosition = cr.cpMin;
		endPosition = cr.cpMax;
	}

	if ((op == ProcessMarkAllExt) && (colourStyleID != -1))
	{
		startPosition = 0;
		endPosition = docLength;
	}

	FindReplaceInfo findReplaceInfo;
	findReplaceInfo._txt2find = txt2find;
	findReplaceInfo._txt2replace = txt2replace;
	findReplaceInfo._startRange = startPosition;
	findReplaceInfo._endRange = endPosition;

	int nbProcessed = processRange(op, findReplaceInfo, pFindersInfo, pOptions, colourStyleID);

	if (nbProcessed == FIND_INVALID_REGULAR_EXPRESSION)
		return FIND_INVALID_REGULAR_EXPRESSION;

	if (nbProcessed > 0 && op == ProcessReplaceAll && pOptions->_isInSelection)
	{
		size_t newDocLength = (*_ppEditView)->execute(SCI_GETLENGTH);
		endPosition += newDocLength - docLength;
		(*_ppEditView)->execute(SCI_SETSELECTION, endPosition, startPosition);
		(*_ppEditView)->execute(SCI_SCROLLRANGE, startPosition, endPosition);
		if (startPosition == endPosition)
		{
			const NppGUI& nppGui = (NppParameters::getInstance()).getNppGUI();
			if (nppGui._inSelectionAutocheckThreshold != 0)
			{
				setChecked(IDC_IN_SELECTION_CHECK, false);
			}
			enableFindDlgItem(IDC_IN_SELECTION_CHECK, false);
		}
	}
	return nbProcessed;
}

int FindReplaceDlg::processRange(ProcessOperation op, FindReplaceInfo & findReplaceInfo, const FindersInfo * pFindersInfo, const FindOption *opt, int colourStyleID, ScintillaEditView *view2Process)
{
	int nbProcessed = 0;

	if (!isCreated() && !findReplaceInfo._txt2find)
		return 0;

	if (!_ppEditView)
		return 0;

	ScintillaEditView *pEditView = *_ppEditView;

	if (view2Process)
		pEditView = view2Process;

	if ((op == ProcessReplaceAll) && pEditView->getCurrentBuffer()->isReadOnly())
		return nbProcessed;

	if (findReplaceInfo._startRange == findReplaceInfo._endRange)
		return nbProcessed;

	const FindOption *pOptions = opt ? opt : _env;

	LRESULT stringSizeFind = 0;
	LRESULT stringSizeReplace = 0;

	TCHAR *pTextFind = NULL;
	if (!findReplaceInfo._txt2find)
	{
		HWND hFindCombo = ::GetDlgItem(_hSelf, IDFINDWHAT);
		generic_string str2Search = getTextFromCombo(hFindCombo);
		stringSizeFind = str2Search.length();
		pTextFind = new TCHAR[stringSizeFind + 1];
		wcscpy_s(pTextFind, stringSizeFind + 1, str2Search.c_str());
	}
	else
	{
		stringSizeFind = lstrlen(findReplaceInfo._txt2find);
		pTextFind = new TCHAR[stringSizeFind + 1];
		wcscpy_s(pTextFind, stringSizeFind + 1, findReplaceInfo._txt2find);
	}

	if (!pTextFind[0])
	{
		delete [] pTextFind;
		return nbProcessed;
	}

	TCHAR *pTextReplace = NULL;
	if (op == ProcessReplaceAll)
	{
		if (!findReplaceInfo._txt2replace)
		{
			HWND hReplaceCombo = ::GetDlgItem(_hSelf, IDREPLACEWITH);
			generic_string str2Replace = getTextFromCombo(hReplaceCombo);
			stringSizeReplace = str2Replace.length();
			pTextReplace = new TCHAR[stringSizeReplace + 1];
			wcscpy_s(pTextReplace, stringSizeReplace + 1, str2Replace.c_str());
		}
		else
		{
			stringSizeReplace = lstrlen(findReplaceInfo._txt2replace);
			pTextReplace = new TCHAR[stringSizeReplace + 1];
			wcscpy_s(pTextReplace, stringSizeReplace + 1, findReplaceInfo._txt2replace);
		}
	}

	if (pOptions->_searchType == FindExtended)
	{
		stringSizeFind = Searching::convertExtendedToString(pTextFind, pTextFind, static_cast<int32_t>(stringSizeFind));
		if (op == ProcessReplaceAll)
			stringSizeReplace = Searching::convertExtendedToString(pTextReplace, pTextReplace, static_cast<int32_t>(stringSizeReplace));
	}

	bool isRegExp = pOptions->_searchType == FindRegex;
	int flags = Searching::buildSearchFlags(pOptions) | SCFIND_REGEXP_SKIPCRLFASONE;

	// Allow empty matches, but not immediately after previous match for replace all or find all.
	// Other search types should ignore empty matches completely.
	if (op == ProcessReplaceAll || op == ProcessFindAll)
		flags |= SCFIND_REGEXP_EMPTYMATCH_NOTAFTERMATCH;



	if (op == ProcessMarkAll && colourStyleID == -1)	//if marking, check if purging is needed
	{
		if (_env->_doPurge)
		{
			clearMarks(*_env);
		}
	}

	intptr_t targetStart = 0;
	intptr_t targetEnd = 0;

	//Initial range for searching
	pEditView->execute(SCI_SETSEARCHFLAGS, flags);


	bool findAllFileNameAdded = false;

	// A temporary string which is used to populate the search result window
	std::unique_ptr<std::string> text2AddUtf8(new std::string());
	size_t indexBuffer = 0;

	while (targetStart >= 0)
	{
		targetStart = pEditView->searchInTarget(pTextFind, stringSizeFind, findReplaceInfo._startRange, findReplaceInfo._endRange);

		// If we've not found anything, just break out of the loop
		if (targetStart == -1)
			break;

		if (targetStart == FIND_INVALID_REGULAR_EXPRESSION)
			return FIND_INVALID_REGULAR_EXPRESSION;

		targetEnd = pEditView->execute(SCI_GETTARGETEND);

		if (targetEnd > findReplaceInfo._endRange)
		{
			//we found a result but outside our range, therefore do not process it
			break;
		}

		intptr_t foundTextLen = targetEnd - targetStart;
		intptr_t replaceDelta = 0;
		bool processed = true;

		switch (op)
		{
			case ProcessFindAll:
			{
				const TCHAR *pFileName = TEXT("");
				if (pFindersInfo && pFindersInfo->_pFileName)
					pFileName = pFindersInfo->_pFileName;

				if (!findAllFileNameAdded)	//add new filetitle in hits if we haven't already
				{
					_pFinder->addFileNameTitle(pFileName);
					findAllFileNameAdded = true;
				}

				auto totalLineNumber = pEditView->execute(SCI_GETLINECOUNT);
				auto lineNumber = pEditView->execute(SCI_LINEFROMPOSITION, targetStart);
				intptr_t lend = pEditView->execute(SCI_GETLINEENDPOSITION, lineNumber);
				intptr_t lstart = pEditView->execute(SCI_POSITIONFROMLINE, lineNumber);
				intptr_t nbChar = lend - lstart;

				// use the static buffer
				TCHAR lineBuf[SC_SEARCHRESULT_LINEBUFFERMAXLENGTH]{};

				if (nbChar > SC_SEARCHRESULT_LINEBUFFERMAXLENGTH - 3)
					lend = lstart + SC_SEARCHRESULT_LINEBUFFERMAXLENGTH - 4;

				intptr_t start_mark = targetStart - lstart;
				intptr_t end_mark = targetEnd - lstart;

				pEditView->getGenericText(lineBuf, SC_SEARCHRESULT_LINEBUFFERMAXLENGTH, lstart, lend, &start_mark, &end_mark);

				generic_string line = lineBuf;
				line += TEXT("\r\n");

				SearchResultMarkingLine srml;
				srml._segmentPostions.push_back(std::pair<intptr_t, intptr_t>(start_mark, end_mark));
				text2AddUtf8->append(_pFinder->foundLine(FoundInfo(targetStart, targetEnd, lineNumber + 1, pFileName), srml, line.c_str(), totalLineNumber));

				if (text2AddUtf8->length() > FINDTEMPSTRING_MAXSIZE)
				{
					_pFinder->setFinderReadOnly(false);
					_pFinder->_scintView.execute(SCI_ADDTEXT, text2AddUtf8->length(), reinterpret_cast<LPARAM>(text2AddUtf8->c_str()));
					_pFinder->setFinderReadOnly(true);
					text2AddUtf8->clear();
				}
				break;
			}

			case ProcessFindInFinder:
			{
				if (!pFindersInfo || !pFindersInfo->_pSourceFinder || !pFindersInfo->_pDestFinder)
					break;

				const TCHAR *pFileName = pFindersInfo->_pFileName ? pFindersInfo->_pFileName : TEXT("");

				auto totalLineNumber = pEditView->execute(SCI_GETLINECOUNT);
				auto lineNumber = pEditView->execute(SCI_LINEFROMPOSITION, targetStart);
				intptr_t lend = pEditView->execute(SCI_GETLINEENDPOSITION, lineNumber);
				intptr_t lstart = pEditView->execute(SCI_POSITIONFROMLINE, lineNumber);
				intptr_t nbChar = lend - lstart;

				// use the static buffer
				TCHAR lineBuf[SC_SEARCHRESULT_LINEBUFFERMAXLENGTH]{};

				if (nbChar > SC_SEARCHRESULT_LINEBUFFERMAXLENGTH - 3)
					lend = lstart + SC_SEARCHRESULT_LINEBUFFERMAXLENGTH - 4;

				intptr_t start_mark = targetStart - lstart;
				intptr_t end_mark = targetEnd - lstart;

				pEditView->getGenericText(lineBuf, SC_SEARCHRESULT_LINEBUFFERMAXLENGTH, lstart, lend, &start_mark, &end_mark);

				generic_string line = lineBuf;
				line += TEXT("\r\n");
				SearchResultMarkingLine srml;
				srml._segmentPostions.push_back(std::pair<intptr_t, intptr_t>(start_mark, end_mark));

				processed = (!pOptions->_isMatchLineNumber) || (pFindersInfo->_pSourceFinder->canFind(pFileName, lineNumber + 1, &indexBuffer));
				if (processed)
				{
					if (!findAllFileNameAdded)	//add new filetitle in hits if we haven't already
					{
						pFindersInfo->_pDestFinder->addFileNameTitle(pFileName);
						findAllFileNameAdded = true;
					}
					text2AddUtf8->append(pFindersInfo->_pDestFinder->foundLine(FoundInfo(targetStart, targetEnd, lineNumber + 1, pFileName), srml, line.c_str(), totalLineNumber));

					if (text2AddUtf8->length() > FINDTEMPSTRING_MAXSIZE)
					{
						pFindersInfo->_pDestFinder->setFinderReadOnly(false);
						pFindersInfo->_pDestFinder->_scintView.execute(SCI_ADDTEXT, text2AddUtf8->length(), reinterpret_cast<LPARAM>(text2AddUtf8->c_str()));
						pFindersInfo->_pDestFinder->setFinderReadOnly(true);
						text2AddUtf8->clear();
					}
				}
				break;
			}

			case ProcessReplaceAll:
			{
				intptr_t replacedLength;
				if (isRegExp)
					replacedLength = pEditView->replaceTargetRegExMode(pTextReplace);
				else
					replacedLength = pEditView->replaceTarget(pTextReplace);

				replaceDelta = replacedLength - foundTextLen;
				break;
			}

			case ProcessMarkAll:
			{
				// In theory, we can't have empty matches for a ProcessMarkAll, but because scintilla
				// gets upset if we call INDICATORFILLRANGE with a length of 0, we protect against it here.
				// At least in version 2.27, after calling INDICATORFILLRANGE with length 0, further indicators
				// on the same line would simply not be shown.  This may have been fixed in later version of Scintilla.
				if (foundTextLen > 0)
				{
					pEditView->execute(SCI_SETINDICATORCURRENT, SCE_UNIVERSAL_FOUND_STYLE);
					pEditView->execute(SCI_INDICATORFILLRANGE,  targetStart, foundTextLen);
				}

				if (_env->_doMarkLine)
				{
					auto lineNumber = pEditView->execute(SCI_LINEFROMPOSITION, targetStart);
					auto lineNumberEnd = pEditView->execute(SCI_LINEFROMPOSITION, targetEnd - 1);

					for (auto i = lineNumber; i <= lineNumberEnd; ++i)
					{
						auto state = pEditView->execute(SCI_MARKERGET, i);

						if (!(state & (1 << MARK_BOOKMARK)))
							pEditView->execute(SCI_MARKERADD, i, MARK_BOOKMARK);
					}
				}
				break;
			}

			case ProcessMarkAllExt:
			{
				// See comment by ProcessMarkAll
				if (foundTextLen > 0)
				{
					pEditView->execute(SCI_SETINDICATORCURRENT,  colourStyleID);
					pEditView->execute(SCI_INDICATORFILLRANGE,  targetStart, foundTextLen);
				}
				break;
			}

			case ProcessMarkAll_2:
			{
				// See comment by ProcessMarkAll
				if (foundTextLen > 0)
				{
					pEditView->execute(SCI_SETINDICATORCURRENT,  SCE_UNIVERSAL_FOUND_STYLE_SMART);
					pEditView->execute(SCI_INDICATORFILLRANGE,  targetStart, foundTextLen);
				}
				break;
			}

			case ProcessMarkAll_IncSearch:
			{
				// See comment by ProcessMarkAll
				if (foundTextLen > 0)
				{
					pEditView->execute(SCI_SETINDICATORCURRENT,  SCE_UNIVERSAL_FOUND_STYLE_INC);
					pEditView->execute(SCI_INDICATORFILLRANGE,  targetStart, foundTextLen);
				}
				break;
			}

			case ProcessCountAll:
			{
				//Nothing to do
				break;
			}

			default:
			{
				delete [] pTextFind;
				delete [] pTextReplace;
				return nbProcessed;
			}

		}
		if (processed) ++nbProcessed;

		// After the processing of the last string occurrence the search loop should be stopped
		// This helps to avoid the endless replacement during the EOL ("$") searching
		if (targetStart + foundTextLen == findReplaceInfo._endRange)
			break;

		findReplaceInfo._startRange = targetStart + foundTextLen + replaceDelta;		//search from result onwards
		findReplaceInfo._endRange += replaceDelta;									//adjust end of range in case of replace
	}

	delete [] pTextFind;
	delete [] pTextReplace;

	if (nbProcessed > 0)
	{
		Finder *pFinder = nullptr;
		if (op == ProcessFindAll)
		{
			_pFinder->setFinderReadOnly(false);
			_pFinder->_scintView.execute(SCI_ADDTEXT, text2AddUtf8->length(), reinterpret_cast<LPARAM>(text2AddUtf8->c_str()));
			_pFinder->setFinderReadOnly(true);
			text2AddUtf8->clear();
			pFinder = _pFinder;
		}
		else if (op == ProcessFindInFinder)
		{
			if (pFindersInfo && pFindersInfo->_pDestFinder)
			{
				pFindersInfo->_pDestFinder->setFinderReadOnly(false);
				pFindersInfo->_pDestFinder->_scintView.execute(SCI_ADDTEXT, text2AddUtf8->length(), reinterpret_cast<LPARAM>(text2AddUtf8->c_str()));
				pFindersInfo->_pDestFinder->setFinderReadOnly(true);
				text2AddUtf8->clear();
				pFinder = pFindersInfo->_pDestFinder;
			}
			else
				pFinder = _pFinder;
		}

		if (pFinder != nullptr)
			pFinder->addFileHitCount(nbProcessed);
	}
	return nbProcessed;
}

void FindReplaceDlg::replaceAllInOpenedDocs()
{
	::SendMessage(_hParent, WM_REPLACEALL_INOPENEDDOC, 0, 0);
}

void FindReplaceDlg::findAllIn(InWhat op)
{
	bool justCreated = false;
	if (!_pFinder)
	{
		_pFinder = new Finder();
		_pFinder->init(_hInst, (*_ppEditView)->getHParent(), _ppEditView);
		_pFinder->setVolatiled(false);

		tTbData	data = {};
		_pFinder->create(&data);
		::SendMessage(_hParent, NPPM_MODELESSDIALOG, MODELESSDIALOGREMOVE, reinterpret_cast<LPARAM>(_pFinder->getHSelf()));
		// define the default docking behaviour
		data.uMask = DWS_DF_CONT_BOTTOM | DWS_ICONTAB | DWS_ADDINFO | DWS_USEOWNDARKMODE;
		data.hIconTab = (HICON)::LoadImage(_hInst, MAKEINTRESOURCE(IDI_FIND_RESULT_ICON), IMAGE_ICON, 0, 0, LR_LOADMAP3DCOLORS | LR_LOADTRANSPARENT);
		data.pszAddInfo = _findAllResultStr;

		data.pszModuleName = NPP_INTERNAL_FUCTION_STR;

		// the dlgDlg should be the index of funcItem where the current function pointer is
		// in this case is DOCKABLE_DEMO_INDEX
		data.dlgID = 0;

		NativeLangSpeaker *pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();
		generic_string text = pNativeSpeaker->getLocalizedStrFromID("find-result-caption", TEXT(""));

		if (!text.empty())
		{
			_findResTitle = text;
			data.pszName = _findResTitle.c_str();
		}

		::SendMessage(_hParent, NPPM_DMMREGASDCKDLG, 0, reinterpret_cast<LPARAM>(&data));

		_pFinder->_scintView.init(_hInst, _pFinder->getHSelf());

		// Subclass the ScintillaEditView for the Finder (Scintilla doesn't notify all key presses)
		originalFinderProc = reinterpret_cast<WNDPROC>(SetWindowLongPtr(_pFinder->_scintView.getHSelf(), GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(finderProc)));

		_pFinder->setFinderReadOnly(true);
		_pFinder->_scintView.execute(SCI_SETCODEPAGE, SC_CP_UTF8);
		_pFinder->_scintView.execute(SCI_USEPOPUP, FALSE);
		_pFinder->_scintView.execute(SCI_SETUNDOCOLLECTION, false);	//dont store any undo information
		_pFinder->_scintView.execute(SCI_SETCARETWIDTH, 1);
		_pFinder->_scintView.showMargin(ScintillaEditView::_SC_MARGE_FOLDER, true);

		_pFinder->_scintView.execute(SCI_SETUSETABS, true);
		_pFinder->_scintView.execute(SCI_SETTABWIDTH, 4);

		NppParameters& nppParam = NppParameters::getInstance();
		NppGUI& nppGUI = nppParam.getNppGUI();
		_pFinder->_longLinesAreWrapped = nppGUI._finderLinesAreCurrentlyWrapped;
		_pFinder->_scintView.wrap(_pFinder->_longLinesAreWrapped);
		_pFinder->_scintView.setWrapMode(LINEWRAP_INDENT);
		_pFinder->_scintView.showWrapSymbol(true);

		_pFinder->_purgeBeforeEverySearch = nppGUI._finderPurgeBeforeEverySearch;

		// allow user to start selecting as a stream block, then switch to a column block by adding Alt keypress
		_pFinder->_scintView.execute(SCI_SETMOUSESELECTIONRECTANGULARSWITCH, true);

		// get the width of FindDlg
		RECT findRect;
		::GetWindowRect(_pFinder->getHSelf(), &findRect);

		// overwrite some default settings
		_pFinder->_scintView.showMargin(ScintillaEditView::_SC_MARGE_SYMBOL, false);
		_pFinder->_scintView.setMakerStyle(FOLDER_STYLE_SIMPLE);

		NppDarkMode::setBorder(_pFinder->_scintView.getHSelf());

		_pFinder->_scintView.display();
		_pFinder->setFinderStyle();
		_pFinder->setFinderStyleForNpc();

		_pFinder->display(false);
		::UpdateWindow(_hParent);
		justCreated = true;
		
	}
	
	if (_pFinder->_purgeBeforeEverySearch)
	{
		_pFinder->removeAll();
	}

	if (justCreated)
	{
		// Send the address of _MarkingsStruct to the lexer
		char ptrword[sizeof(void*) * 2 + 1];
		sprintf(ptrword, "%p", static_cast<void*>(&_pFinder->_markingsStruct));
		_pFinder->_scintView.execute(SCI_SETPROPERTY, reinterpret_cast<WPARAM>("@MarkingsStruct"), reinterpret_cast<LPARAM>(ptrword));

		//enable "Search Results Window" under Search Menu
		::EnableMenuItem(::GetMenu(_hParent), IDM_FOCUS_ON_FOUND_RESULTS, MF_ENABLED | MF_BYCOMMAND);
	}

	::SendMessage(_pFinder->getHSelf(), WM_SIZE, 0, 0);

	bool toRTL = (*_ppEditView)->isTextDirectionRTL();
	bool isRTL = _pFinder->_scintView.isTextDirectionRTL();

	if ((toRTL && !isRTL) || (!toRTL && isRTL))
		_pFinder->_scintView.changeTextDirection(toRTL);

	int cmdid = 0;
	if (op == ALL_OPEN_DOCS)
		cmdid = WM_FINDALL_INOPENEDDOC;
	else if (op == FILES_IN_DIR)
		cmdid = WM_FINDINFILES;
	else if (op == FILES_IN_PROJECTS)
		cmdid = WM_FINDINPROJECTS;
	else if ((op == CURRENT_DOC) || (op == CURR_DOC_SELECTION))
		cmdid = WM_FINDALL_INCURRENTDOC;

	if (!cmdid) return;

	bool limitSearchScopeToSelection = op == CURR_DOC_SELECTION;
	if (::SendMessage(_hParent, cmdid, static_cast<WPARAM>(limitSearchScopeToSelection ? 1 : 0), 0))
	{
		generic_string text = _pFinder->getHitsString(_findAllResult);
		wsprintf(_findAllResultStr, text.c_str());

		if (_findAllResult)
		{
			focusOnFinder();
		}
		else
		{
			// Show finder
			_pFinder->display();
			getFocus(); // no hits
		}
	}
	else // error - search folder doesn't exist
		::SendMessage(_hSelf, WM_NEXTDLGCTL, reinterpret_cast<WPARAM>(::GetDlgItem(_hSelf, IDD_FINDINFILES_DIR_COMBO)), TRUE);
}

Finder * FindReplaceDlg::createFinder()
{
	Finder *pFinder = new Finder();
	pFinder->init(_hInst, (*_ppEditView)->getHParent(), _ppEditView);

	tTbData	data = {};
	bool isRTL = _pFinder->_scintView.isTextDirectionRTL();
	pFinder->create(&data, isRTL);
	::SendMessage(_hParent, NPPM_MODELESSDIALOG, MODELESSDIALOGREMOVE, reinterpret_cast<WPARAM>(pFinder->getHSelf()));
	// define the default docking behaviour
	data.uMask = DWS_DF_CONT_BOTTOM | DWS_ICONTAB | DWS_ADDINFO | DWS_USEOWNDARKMODE;
	data.hIconTab = (HICON)::LoadImage(_hInst, MAKEINTRESOURCE(IDI_FIND_RESULT_ICON), IMAGE_ICON, 0, 0, LR_LOADMAP3DCOLORS | LR_LOADTRANSPARENT);
	data.pszAddInfo = _findAllResultStr;

	data.pszModuleName = NPP_INTERNAL_FUCTION_STR;

	// the dlgDlg should be the index of funcItem where the current function pointer is
	// in this case is DOCKABLE_DEMO_INDEX
	data.dlgID = 0;

	NativeLangSpeaker *pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();
	generic_string text = pNativeSpeaker->getLocalizedStrFromID("find-result-caption", TEXT(""));
	if (!text.empty())
	{
		_findResTitle = text;
		data.pszName = _findResTitle.c_str();
	}

	::SendMessage(_hParent, NPPM_DMMREGASDCKDLG, 0, reinterpret_cast<LPARAM>(&data));

	pFinder->_scintView.init(_hInst, pFinder->getHSelf());
	if (isRTL)
		pFinder->_scintView.changeTextDirection(true);

	// Subclass the ScintillaEditView for the Finder (Scintilla doesn't notify all key presses)
	originalFinderProc = reinterpret_cast<WNDPROC>(SetWindowLongPtr(pFinder->_scintView.getHSelf(), GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(finderProc)));

	pFinder->setFinderReadOnly(true);
	pFinder->_scintView.execute(SCI_SETCODEPAGE, SC_CP_UTF8);
	pFinder->_scintView.execute(SCI_USEPOPUP, FALSE);
	pFinder->_scintView.execute(SCI_SETUNDOCOLLECTION, false);	//dont store any undo information
	pFinder->_scintView.execute(SCI_SETCARETWIDTH, 1);
	pFinder->_scintView.showMargin(ScintillaEditView::_SC_MARGE_FOLDER, true);

	pFinder->_scintView.execute(SCI_SETUSETABS, true);
	pFinder->_scintView.execute(SCI_SETTABWIDTH, 4);

	// inherit setting from current state of main finder:
	pFinder->_longLinesAreWrapped = _pFinder->_longLinesAreWrapped;
	pFinder->_scintView.wrap(pFinder->_longLinesAreWrapped);
	pFinder->_scintView.setWrapMode(LINEWRAP_INDENT);
	pFinder->_scintView.showWrapSymbol(true);

	// allow user to start selecting as a stream block, then switch to a column block by adding Alt keypress
	pFinder->_scintView.execute(SCI_SETMOUSESELECTIONRECTANGULARSWITCH, true);

	// get the width of FindDlg
	RECT findRect;
	::GetWindowRect(pFinder->getHSelf(), &findRect);

	// overwrite some default settings
	pFinder->_scintView.showMargin(ScintillaEditView::_SC_MARGE_SYMBOL, false);
	pFinder->_scintView.setMakerStyle(FOLDER_STYLE_SIMPLE);

	pFinder->_scintView.display();
	::UpdateWindow(_hParent);

	NppDarkMode::setBorder(pFinder->_scintView.getHSelf());
	pFinder->setFinderStyle();
	pFinder->setFinderStyleForNpc();

	// Send the address of _MarkingsStruct to the lexer
	char ptrword[sizeof(void*) * 2 + 1];
	sprintf(ptrword, "%p", static_cast<void*>(&pFinder->_markingsStruct));
	pFinder->_scintView.execute(SCI_SETPROPERTY, reinterpret_cast<WPARAM>("@MarkingsStruct"), reinterpret_cast<LPARAM>(ptrword));

	_findersOfFinder.push_back(pFinder);

	::SendMessage(pFinder->getHSelf(), WM_SIZE, 0, 0);

	// Show finder
	pFinder->display();
	pFinder->_scintView.getFocus();

	return pFinder;
}

bool FindReplaceDlg::removeFinder(Finder *finder2remove)
{
	for (vector<Finder *>::iterator i = _findersOfFinder.begin(); i != _findersOfFinder.end(); ++i)
	{
		if (*i == finder2remove)
		{
			delete finder2remove;
			_findersOfFinder.erase(i);
			return true;
		}
	}
	return false;
}

void FindReplaceDlg::setSearchText(TCHAR * txt2find)
{
	HWND hCombo = ::GetDlgItem(_hSelf, IDFINDWHAT);
	if (txt2find && txt2find[0])
	{
		// We got a valid search string
		::SendMessage(hCombo, CB_SETCURSEL, static_cast<WPARAM>(-1), 0); // remove selection - to allow using down arrow to get to last searched word
		::SetDlgItemText(_hSelf, IDFINDWHAT, txt2find);
	}
	::SendMessage(hCombo, CB_SETEDITSEL, 0, MAKELPARAM(0, -1)); // select all text - fast edit
}

void FindReplaceDlg::enableFindDlgItem(int dlgItemID, bool isEnable /* = true*/)
{
	HWND h = ::GetDlgItem(_hSelf, dlgItemID);
	if (!h) return;

	::EnableWindow(h, isEnable ? TRUE : FALSE);

	// remember the real state of this control being enabled/disabled
	_controlEnableMap[dlgItemID] = isEnable;
}

void FindReplaceDlg::showFindDlgItem(int dlgItemID, bool isShow /* = true*/)
{
	HWND h = ::GetDlgItem(_hSelf, dlgItemID);
	if (!h) return;

	::ShowWindow(h, isShow ? SW_SHOW : SW_HIDE);

	// when hiding a control to make it user-inaccessible, it can still be manipulated via a keyboard accelerator!
	// so disable it in addition to hiding it to prevent such user interaction
	// but that causes trouble when unhiding it; we don't know if it should be enabled or disabled,
	// so used the remembered state when we last enabled/disabled this control to determine it

	if (dlgItemID == IDOK)
	{
		// do not disable the standard Find-Next button (IDOK);
		// keeping it enabled allows Enter (and Shift+Enter) to work when in 2-button-find-mode
		// and IDC_FINDNEXT does not have focus
		return;
	}

	int enable = isShow ? TRUE : FALSE;

	if (isShow)
	{
		const auto iter = _controlEnableMap.find(dlgItemID);
		if (iter == _controlEnableMap.end())
		{
			// if control's state was never previously recorded, assume it was enabled
			enable = TRUE;
			_controlEnableMap[dlgItemID] = true;
		}
		else
		{
			enable = iter->second ? TRUE : FALSE;
		}
	}

	::EnableWindow(h, enable);
}

void FindReplaceDlg::enableReplaceFunc(bool isEnable)
{
	_currentStatus = isEnable?REPLACE_DLG:FIND_DLG;
	RECT *pClosePos = isEnable ? &_replaceClosePos : &_findClosePos;
	RECT *pInSelectionFramePos = isEnable ? &_replaceInSelFramePos : &_countInSelFramePos;
	RECT *pInSectionCheckPos = isEnable ? &_replaceInSelCheckPos : &_countInSelCheckPos;

	enableFindInFilesControls(false, false);
	enableMarkAllControls(false);
	// replace controls
	showFindDlgItem(ID_STATICTEXT_REPLACE, isEnable);
	showFindDlgItem(IDREPLACE, isEnable);
	showFindDlgItem(IDREPLACEWITH, isEnable);
	showFindDlgItem(IDD_FINDREPLACE_SWAP_BUTTON, isEnable);
	showFindDlgItem(IDREPLACEALL, isEnable);
	showFindDlgItem(IDC_REPLACE_OPENEDFILES, isEnable);
	showFindDlgItem(IDC_REPLACEINSELECTION);
	showFindDlgItem(IDC_IN_SELECTION_CHECK);
	showFindDlgItem(IDC_2_BUTTONS_MODE);
	bool is2ButtonMode = isCheckedOrNot(IDC_2_BUTTONS_MODE);
	showFindDlgItem(IDOK, !is2ButtonMode);
	showFindDlgItem(IDC_FINDPREV, is2ButtonMode);
	showFindDlgItem(IDC_FINDNEXT, is2ButtonMode);


	// find controls
	showFindDlgItem(IDC_FINDALL_OPENEDFILES, !isEnable);
	showFindDlgItem(IDCCOUNTALL, !isEnable);
	showFindDlgItem(IDC_FINDALL_CURRENTFILE, !isEnable);

	gotoCorrectTab();

	::MoveWindow(::GetDlgItem(_hSelf, IDCANCEL), pClosePos->left + _deltaWidth, pClosePos->top, pClosePos->right, pClosePos->bottom, TRUE);
	::MoveWindow(::GetDlgItem(_hSelf, IDC_IN_SELECTION_CHECK), pInSectionCheckPos->left + _deltaWidth, pInSectionCheckPos->top, pInSectionCheckPos->right, pInSectionCheckPos->bottom, TRUE);
	::MoveWindow(::GetDlgItem(_hSelf, IDC_REPLACEINSELECTION), pInSelectionFramePos->left + _deltaWidth, pInSelectionFramePos->top, pInSelectionFramePos->right, pInSelectionFramePos->bottom, TRUE);

	TCHAR label[MAX_PATH] = { '\0' };
	_tab.getCurrentTitle(label, MAX_PATH);
	::SetWindowText(_hSelf, label);

	setDefaultButton(IDOK);

	hideOrShowCtrl4reduceOrNormalMode(_currentStatus);
}

void FindReplaceDlg::enableMarkAllControls(bool isEnable)
{
	showFindDlgItem(IDCMARKALL, isEnable);
	showFindDlgItem(IDC_MARKLINE_CHECK, isEnable);
	showFindDlgItem(IDC_PURGE_CHECK, isEnable);
	showFindDlgItem(IDC_CLEAR_ALL, isEnable);
	showFindDlgItem(IDC_IN_SELECTION_CHECK, isEnable);
	showFindDlgItem(IDC_COPY_MARKED_TEXT, isEnable);
}

void FindReplaceDlg::enableFindInFilesControls(bool isEnable, bool projectPanels)
{
	// Hide Items
	showFindDlgItem(IDC_BACKWARDDIRECTION, !isEnable);
	showFindDlgItem(IDWRAP, !isEnable);
	showFindDlgItem(IDCCOUNTALL, !isEnable);
	showFindDlgItem(IDC_FINDALL_OPENEDFILES, !isEnable);
	showFindDlgItem(IDC_FINDALL_CURRENTFILE, !isEnable);

	if (isEnable)
	{
		showFindDlgItem(IDC_2_BUTTONS_MODE, false);
		showFindDlgItem(IDOK, false);
		showFindDlgItem(IDC_FINDPREV, false);
		showFindDlgItem(IDC_FINDNEXT, false);
	}
	else
	{
		showFindDlgItem(IDC_2_BUTTONS_MODE);
		bool is2ButtonMode = isCheckedOrNot(IDC_2_BUTTONS_MODE);
		showFindDlgItem(IDOK, !is2ButtonMode);
		showFindDlgItem(IDC_FINDPREV, is2ButtonMode);
		showFindDlgItem(IDC_FINDNEXT, is2ButtonMode);
	}

	showFindDlgItem(IDC_MARKLINE_CHECK, !isEnable);
	showFindDlgItem(IDC_PURGE_CHECK, !isEnable);
	showFindDlgItem(IDC_IN_SELECTION_CHECK, !isEnable);
	showFindDlgItem(IDC_CLEAR_ALL, !isEnable);
	showFindDlgItem(IDCMARKALL, !isEnable);
	showFindDlgItem(IDC_COPY_MARKED_TEXT, !isEnable);

	showFindDlgItem(IDREPLACE, !isEnable);
	showFindDlgItem(IDC_REPLACEINSELECTION, !isEnable);
	showFindDlgItem(IDREPLACEALL, !isEnable);
	showFindDlgItem(IDC_REPLACE_OPENEDFILES, !isEnable);

	// Show Items
	if (isEnable)
	{
		showFindDlgItem(ID_STATICTEXT_REPLACE);
		showFindDlgItem(IDREPLACEWITH);
		showFindDlgItem(IDD_FINDREPLACE_SWAP_BUTTON);
	}
	showFindDlgItem(IDD_FINDINFILES_REPLACEINFILES, isEnable && (!projectPanels));
	showFindDlgItem(IDD_FINDINFILES_REPLACEINPROJECTS, isEnable && projectPanels);
	showFindDlgItem(IDD_FINDINFILES_FILTERS_STATIC, isEnable);
	showFindDlgItem(IDD_FINDINFILES_FILTERS_COMBO, isEnable);
	showFindDlgItem(IDD_FINDINFILES_DIR_STATIC, isEnable && (!projectPanels));
	showFindDlgItem(IDD_FINDINFILES_DIR_COMBO, isEnable && (!projectPanels));
	showFindDlgItem(IDD_FINDINFILES_BROWSE_BUTTON, isEnable && (!projectPanels));
	showFindDlgItem(IDD_FINDINFILES_FIND_BUTTON, isEnable);
	showFindDlgItem(IDD_FINDINFILES_RECURSIVE_CHECK, isEnable && (!projectPanels));
	showFindDlgItem(IDD_FINDINFILES_INHIDDENDIR_CHECK, isEnable && (!projectPanels));
	showFindDlgItem(IDD_FINDINFILES_PROJECT1_CHECK, isEnable && projectPanels);
	showFindDlgItem(IDD_FINDINFILES_PROJECT2_CHECK, isEnable && projectPanels);
	showFindDlgItem(IDD_FINDINFILES_PROJECT3_CHECK, isEnable && projectPanels);
	showFindDlgItem(IDD_FINDINFILES_FOLDERFOLLOWSDOC_CHECK, isEnable && (!projectPanels));
}

void FindReplaceDlg::getPatterns(vector<generic_string> & patternVect)
{
	cutString(_env->_filters.c_str(), patternVect);
}

void FindReplaceDlg::getAndValidatePatterns(vector<generic_string> & patternVect)
{
	getPatterns(patternVect);
	if (patternVect.size() == 0)
	{
		setFindInFilesDirFilter(NULL, TEXT("*.*"));
		getPatterns(patternVect);
	}
	else if (allPatternsAreExclusion(patternVect))
	{
		patternVect.insert(patternVect.begin(), TEXT("*.*"));
	}
}

void FindReplaceDlg::saveInMacro(size_t cmd, int cmdType)
{
	int booleans = 0;
	::SendMessage(_hParent, WM_FRSAVE_INT, IDC_FRCOMMAND_INIT, 0);
	::SendMessage(_hParent, WM_FRSAVE_STR, IDFINDWHAT,  reinterpret_cast<LPARAM>(cmd == IDC_CLEAR_ALL ? "" : wstring2string(_options._str2Search, CP_UTF8).c_str()));
	booleans |= _options._isWholeWord?IDF_WHOLEWORD:0;
	booleans |= _options._isMatchCase?IDF_MATCHCASE:0;
	booleans |= _options._dotMatchesNewline?IDF_REDOTMATCHNL:0;

	::SendMessage(_hParent, WM_FRSAVE_INT, IDNORMAL, _options._searchType);
	if (cmd == IDCMARKALL)
	{
		booleans |= _options._doPurge?IDF_PURGE_CHECK:0;
		booleans |= _options._doMarkLine?IDF_MARKLINE_CHECK:0;
	}
	if (cmdType & FR_OP_REPLACE)
		::SendMessage(_hParent, WM_FRSAVE_STR, IDREPLACEWITH, reinterpret_cast<LPARAM>(wstring2string(_options._str4Replace, CP_UTF8).c_str()));
	if (cmdType & FR_OP_FIF)
	{
		::SendMessage(_hParent, WM_FRSAVE_STR, IDD_FINDINFILES_DIR_COMBO, reinterpret_cast<LPARAM>(wstring2string(_options._directory, CP_UTF8).c_str()));
		::SendMessage(_hParent, WM_FRSAVE_STR, IDD_FINDINFILES_FILTERS_COMBO, reinterpret_cast<LPARAM>(wstring2string(_options._filters, CP_UTF8).c_str()));
		booleans |= _options._isRecursive?IDF_FINDINFILES_RECURSIVE_CHECK:0;
		booleans |= _options._isInHiddenDir?IDF_FINDINFILES_INHIDDENDIR_CHECK:0;
	}
	if (cmdType & FR_OP_FIP)
	{
		::SendMessage(_hParent, WM_FRSAVE_STR, IDD_FINDINFILES_FILTERS_COMBO, reinterpret_cast<LPARAM>(wstring2string(_options._filters, CP_UTF8).c_str()));
		booleans |= _options._isProjectPanel_1?IDF_FINDINFILES_PROJECT1_CHECK:0;
		booleans |= _options._isProjectPanel_2?IDF_FINDINFILES_PROJECT2_CHECK:0;
		booleans |= _options._isProjectPanel_3?IDF_FINDINFILES_PROJECT3_CHECK:0;
	}
	else if (!(cmdType & FR_OP_GLOBAL))
	{
		booleans |= _options._isInSelection?IDF_IN_SELECTION_CHECK:0;
		booleans |= _options._isWrapAround?IDF_WRAP:0;
		booleans |= _options._whichDirection?IDF_WHICH_DIRECTION:0;
	}
	else if (cmdType == FR_OP_FIND + FR_OP_GLOBAL)
	{
		// find all in curr doc can be limited by selected text
		booleans |= _options._isInSelection ? IDF_IN_SELECTION_CHECK : 0;
	}
	if (cmd == IDC_CLEAR_ALL)
	{
		booleans = _options._doMarkLine ? IDF_MARKLINE_CHECK : 0;
		booleans |= _options._isInSelection ? IDF_IN_SELECTION_CHECK : 0;
	}
	::SendMessage(_hParent, WM_FRSAVE_INT, IDC_FRCOMMAND_BOOLEANS, booleans);
	::SendMessage(_hParent, WM_FRSAVE_INT, IDC_FRCOMMAND_EXEC, cmd);
}

void FindReplaceDlg::setStatusbarMessage(const generic_string & msg, FindStatus staus, char const *pTooltipMsg)
{
	if (_statusbarTooltipWnd)
	{
		::DestroyWindow(_statusbarTooltipWnd);
		_statusbarTooltipWnd = nullptr;
	}
	_statusbarTooltipMsg = (pTooltipMsg && (*pTooltipMsg)) ? s2ws(pTooltipMsg) : TEXT("");

	if (staus == FSNotFound)
	{
		if (!NppParameters::getInstance().getNppGUI()._muteSounds)
			::MessageBeep(0xFFFFFFFF);

		FLASHWINFO flashInfo{};
		flashInfo.cbSize = sizeof(FLASHWINFO);
		flashInfo.hwnd = isVisible()?_hSelf:GetParent(_hSelf);
		flashInfo.uCount = 3;
		flashInfo.dwTimeout = 100;
		flashInfo.dwFlags = FLASHW_ALL;
		FlashWindowEx(&flashInfo);
	}
	else if (staus == FSTopReached || staus == FSEndReached)
	{
		if (!isVisible())
		{
			FLASHWINFO flashInfo{};
			flashInfo.cbSize = sizeof(FLASHWINFO);
			flashInfo.hwnd = GetParent(_hSelf);
			flashInfo.uCount = 2;
			flashInfo.dwTimeout = 100;
			flashInfo.dwFlags = FLASHW_ALL;
			FlashWindowEx(&flashInfo);
		}
	}

	if (isVisible())
	{
		_statusbarFindStatus = staus;
		_statusBar.setOwnerDrawText(msg.c_str());
	}
}

void FindReplaceDlg::setStatusbarMessageWithRegExprErr(ScintillaEditView* pEditView)
{
	if (!pEditView)
		return;

	char msg[511] {};
	pEditView->execute(SCI_GETBOOSTREGEXERRMSG, _countof(msg), reinterpret_cast<LPARAM>(msg));

	NativeLangSpeaker* pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();
	std::wstring result = pNativeSpeaker->getLocalizedStrFromID("find-status-invalid-re", TEXT("Find: Invalid Regular Expression"));

	setStatusbarMessage(result, FSNotFound, msg);
}

generic_string FindReplaceDlg::getScopeInfoForStatusBar(FindOption const *pFindOpt) const
{
	generic_string scope;

	NativeLangSpeaker* pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();

	if (pFindOpt->_isInSelection)
	{
		scope += pNativeSpeaker->getLocalizedStrFromID("find-status-scope-selection", TEXT("in selected text"));
	}
	else if (pFindOpt->_isWrapAround)
	{
		scope += pNativeSpeaker->getLocalizedStrFromID("find-status-scope-all", TEXT("in entire file"));
	}
	else if (pFindOpt->_whichDirection == DIR_UP)
	{
		scope += pNativeSpeaker->getLocalizedStrFromID("find-status-scope-backward", TEXT("from start-of-file to caret"));
	}
	else
	{
		scope += pNativeSpeaker->getLocalizedStrFromID("find-status-scope-forward", TEXT("from caret to end-of-file"));
	}

	return scope;
}

void FindReplaceDlg::execSavedCommand(int cmd, uptr_t intValue, const generic_string& stringValue)
{
	try
	{
		switch (cmd)
		{
			case IDC_FRCOMMAND_INIT:
				_env = new FindOption;
				break;
			case IDFINDWHAT:
				_env->_str2Search = stringValue;
				break;
			case IDC_FRCOMMAND_BOOLEANS:
				_env->_isWholeWord = ((intValue & IDF_WHOLEWORD) > 0);
				_env->_isMatchCase = ((intValue & IDF_MATCHCASE) > 0);
				_env->_isRecursive = ((intValue & IDF_FINDINFILES_RECURSIVE_CHECK) > 0);
				_env->_isInHiddenDir = ((intValue & IDF_FINDINFILES_INHIDDENDIR_CHECK) > 0);
				_env->_isProjectPanel_1 = ((intValue & IDF_FINDINFILES_PROJECT1_CHECK) > 0);
				_env->_isProjectPanel_2 = ((intValue & IDF_FINDINFILES_PROJECT2_CHECK) > 0);
				_env->_isProjectPanel_3 = ((intValue & IDF_FINDINFILES_PROJECT3_CHECK) > 0);
				_env->_doPurge = ((intValue & IDF_PURGE_CHECK) > 0);
				_env->_doMarkLine = ((intValue & IDF_MARKLINE_CHECK) > 0);
				_env->_isInSelection = ((intValue & IDF_IN_SELECTION_CHECK) > 0);
				_env->_isWrapAround = ((intValue & IDF_WRAP) > 0);
				_env->_whichDirection = ((intValue & IDF_WHICH_DIRECTION) > 0);
				_env->_dotMatchesNewline = ((intValue & IDF_REDOTMATCHNL) > 0);
				break;
			case IDNORMAL:
				_env->_searchType = static_cast<SearchType>(intValue);
				break;
			case IDREPLACEWITH:
				_env->_str4Replace = stringValue;
				break;
			case IDD_FINDINFILES_DIR_COMBO:
				_env->_directory = stringValue;
				break;
			case IDD_FINDINFILES_FILTERS_COMBO:
				_env->_filters = stringValue;
				break;
			case IDC_FRCOMMAND_EXEC:
			{
				NppParameters& nppParamInst = NppParameters::getInstance();
				switch (intValue)
				{
					case IDOK:
						if ((_env->_whichDirection == DIR_UP) && (_env->_searchType == FindRegex) && !nppParamInst.regexBackward4PowerUser())
						{
							// regex upward search is disabled
							// this macro step could have been recorded in an earlier version before it was not allowed, or hand-edited
							// make this a no-action macro step
						}
						else
						{
							nppParamInst._isFindReplacing = true;
							processFindNext(_env->_str2Search.c_str());
							nppParamInst._isFindReplacing = false;
						}
						break;

					case IDC_FINDNEXT:
						// IDC_FINDNEXT will not be recorded into new macros recorded with 7.8.5 and later
						// stay playback compatible with 7.5.5 - 7.8.4 where IDC_FINDNEXT was allowed but unneeded/undocumented
						nppParamInst._isFindReplacing = true;
						_env->_whichDirection = DIR_DOWN;
						processFindNext(_env->_str2Search.c_str());
						nppParamInst._isFindReplacing = false;
						break;

					case IDC_FINDPREV:
						// IDC_FINDPREV will not be recorded into new macros recorded with 7.8.5 and later
						// stay playback compatible with 7.5.5 - 7.8.4 where IDC_FINDPREV was allowed but unneeded/undocumented
						if (_env->_searchType == FindRegex && !nppParamInst.regexBackward4PowerUser())
						{
							// regex upward search is disabled
							// this macro step could have been recorded in an earlier version before it was not allowed, or hand-edited
							// make this a no-action macro step
						}
						else
						{
							_env->_whichDirection = DIR_UP;
							nppParamInst._isFindReplacing = true;
							processFindNext(_env->_str2Search.c_str());
							nppParamInst._isFindReplacing = false;
						}
						break;

					case IDREPLACE:
						if ((_env->_whichDirection == DIR_UP) && (_env->_searchType == FindRegex && !nppParamInst.regexBackward4PowerUser()))
						{
							// regex upward search is disabled
							// this macro step could have been recorded in an earlier version before it was disabled, or hand-edited
							// make this a no-action macro step
						}
						else
						{
							nppParamInst._isFindReplacing = true;
							processReplace(_env->_str2Search.c_str(), _env->_str4Replace.c_str(), _env);
							nppParamInst._isFindReplacing = false;
						}
						break;
					case IDC_FINDALL_OPENEDFILES:
						nppParamInst._isFindReplacing = true;
						findAllIn(ALL_OPEN_DOCS);
						nppParamInst._isFindReplacing = false;
						break;
					case IDC_FINDALL_CURRENTFILE:
						nppParamInst._isFindReplacing = true;
						findAllIn(_env->_isInSelection ? CURR_DOC_SELECTION : CURRENT_DOC);
						nppParamInst._isFindReplacing = false;
						break;
					case IDC_REPLACE_OPENEDFILES:
					{
						NppParameters& nppParam = NppParameters::getInstance();
						const NppGUI& nppGui = nppParam.getNppGUI();
						if (!nppGui._confirmReplaceInAllOpenDocs || replaceInOpenDocsConfirmCheck())
						{
							nppParamInst._isFindReplacing = true;
							replaceAllInOpenedDocs();
							nppParamInst._isFindReplacing = false;
						}
						break;
					}
					case IDD_FINDINFILES_FIND_BUTTON:
						nppParamInst._isFindReplacing = true;
						findAllIn(FILES_IN_DIR);
						nppParamInst._isFindReplacing = false;
						break;
					case IDD_FINDINFILES_FINDINPROJECTS:
						nppParamInst._isFindReplacing = true;
						findAllIn(FILES_IN_PROJECTS);
						nppParamInst._isFindReplacing = false;
						break;

					case IDD_FINDINFILES_REPLACEINFILES:
					{
						if (replaceInFilesConfirmCheck(_env->_directory, _env->_filters))
						{
							nppParamInst._isFindReplacing = true;
							::SendMessage(_hParent, WM_REPLACEINFILES, 0, 0);
							nppParamInst._isFindReplacing = false;
						}
						break;
					}

					case IDD_FINDINFILES_REPLACEINPROJECTS:
					{
						if (replaceInProjectsConfirmCheck())
						{
							nppParamInst._isFindReplacing = true;
							::SendMessage(_hParent, WM_REPLACEINPROJECTS, 0, 0);
							nppParamInst._isFindReplacing = false;
						}
						break;
					}

					case IDREPLACEALL:
					{
						nppParamInst._isFindReplacing = true;
						(*_ppEditView)->execute(SCI_BEGINUNDOACTION);
						int nbReplaced = processAll(ProcessReplaceAll, _env);
						(*_ppEditView)->execute(SCI_ENDUNDOACTION);
						nppParamInst._isFindReplacing = false;

						if (nbReplaced == FIND_INVALID_REGULAR_EXPRESSION)
						{	
							setStatusbarMessageWithRegExprErr(*_ppEditView);
						}
						else
						{
							wstring result;
							NativeLangSpeaker* pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();

							if (nbReplaced == 1)
							{
								result = pNativeSpeaker->getLocalizedStrFromID("find-status-replaceall-1-replaced", TEXT("Replace All: 1 occurrence was replaced"));
							}
							else //if (nbReplaced == 0 || nbReplaced > 1)
							{
								result = pNativeSpeaker->getLocalizedStrFromID("find-status-replaceall-nb-replaced", TEXT("Replace All: $INT_REPLACE$ occurrences were replaced"));
								result = stringReplace(result, TEXT("$INT_REPLACE$"), std::to_wstring(nbReplaced));
							}
							result += TEXT(" ");
							result += getScopeInfoForStatusBar(_env);

							setStatusbarMessage(result, FSMessage);
						}

						break;
					}

					case IDCCOUNTALL:
					{
						int nbCounted = processAll(ProcessCountAll, _env);

						if (nbCounted == FIND_INVALID_REGULAR_EXPRESSION)
						{
							setStatusbarMessageWithRegExprErr(*_ppEditView);
						}
						else
						{
							wstring result;
							NativeLangSpeaker* pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();

							if (nbCounted == 1)
							{
								result = pNativeSpeaker->getLocalizedStrFromID("find-status-count-1-match", TEXT("Count: 1 match"));
							}
							else
							{
								result = pNativeSpeaker->getLocalizedStrFromID("find-status-count-nb-matches", TEXT("Count: $INT_REPLACE$ matches"));
								result = stringReplace(result, TEXT("$INT_REPLACE$"), std::to_wstring(nbCounted));
							}
							result += TEXT(" ");
							result += getScopeInfoForStatusBar(_env);

							setStatusbarMessage(result, FSMessage);
						}
						break;
					}

					case IDCMARKALL:
					{
						nppParamInst._isFindReplacing = true;
						int nbMarked = processAll(ProcessMarkAll, _env);
						nppParamInst._isFindReplacing = false;

						if (nbMarked == FIND_INVALID_REGULAR_EXPRESSION)
						{
							setStatusbarMessageWithRegExprErr(*_ppEditView);
						}
						else
						{
							wstring result;
							NativeLangSpeaker* pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();
							if (nbMarked == 1)
							{
								result = pNativeSpeaker->getLocalizedStrFromID("find-status-mark-1-match", TEXT("Mark: 1 match"));
							}
							else
							{
								result = pNativeSpeaker->getLocalizedStrFromID("find-status-mark-nb-matches", TEXT("Mark: $INT_REPLACE$ matches"));
								result = stringReplace(result, TEXT("$INT_REPLACE$"), std::to_wstring(nbMarked));
							}
							result += TEXT(" ");
							result += getScopeInfoForStatusBar(_env);

							setStatusbarMessage(result, FSMessage);
						}

						break;
					}

					case IDC_CLEAR_ALL:
					{
						clearMarks(*_env);
						break;
					}

					default:
						throw std::runtime_error("Internal error: unknown saved command!");
				}

				delete _env;
				_env = &_options;
				break;
			}
			default:
				throw std::runtime_error("Internal error: unknown SnR command!");
		}
	}
	catch (const std::runtime_error& err)
	{
		MessageBoxA(NULL, err.what(), "Play Macro Exception", MB_OK);
	}
}

void FindReplaceDlg::clearMarks(const FindOption& opt)
{
	if (opt._isInSelection)
	{
		Sci_CharacterRangeFull cr = (*_ppEditView)->getSelection();

		intptr_t startPosition = cr.cpMin;
		intptr_t endPosition = cr.cpMax;

		(*_ppEditView)->execute(SCI_SETINDICATORCURRENT, SCE_UNIVERSAL_FOUND_STYLE);
		(*_ppEditView)->execute(SCI_INDICATORCLEARRANGE, startPosition, endPosition - startPosition);

		if (opt._doMarkLine)
		{
			auto lineNumber = (*_ppEditView)->execute(SCI_LINEFROMPOSITION, startPosition);
			auto lineNumberEnd = (*_ppEditView)->execute(SCI_LINEFROMPOSITION, endPosition - 1);

			for (auto i = lineNumber; i <= lineNumberEnd; ++i)
			{
				auto state = (*_ppEditView)->execute(SCI_MARKERGET, i);

				if (state & (1 << MARK_BOOKMARK))
					(*_ppEditView)->execute(SCI_MARKERDELETE, i, MARK_BOOKMARK);
			}
		}
	}
	else
	{
		(*_ppEditView)->clearIndicator(SCE_UNIVERSAL_FOUND_STYLE);
		if (opt._doMarkLine)
		{
			(*_ppEditView)->execute(SCI_MARKERDELETEALL, MARK_BOOKMARK);
		}
	}

	setStatusbarMessage(TEXT(""), FSNoMessage);
}

void FindReplaceDlg::setFindInFilesDirFilter(const TCHAR *dir, const TCHAR *filters)
{
	if (dir)
	{
		_options._directory = dir;
		::SetDlgItemText(_hSelf, IDD_FINDINFILES_DIR_COMBO, dir);
	}
	if (filters)
	{
		_options._filters = filters;
		::SetDlgItemText(_hSelf, IDD_FINDINFILES_FILTERS_COMBO, filters);
	}
}

void FindReplaceDlg::enableProjectCheckmarks()
{
	NppParameters& nppParams = NppParameters::getInstance();
	FindHistory & findHistory = nppParams.getFindHistory();
	HMENU hMenu = (HMENU) ::SendMessage (_hParent, NPPM_INTERNAL_GETMENU, 0, 0);
	if (hMenu)
	{
		int   idm [3] = {IDM_VIEW_PROJECT_PANEL_1, IDM_VIEW_PROJECT_PANEL_2, IDM_VIEW_PROJECT_PANEL_3};
		int   idd [3] = {IDD_FINDINFILES_PROJECT1_CHECK, IDD_FINDINFILES_PROJECT2_CHECK, IDD_FINDINFILES_PROJECT3_CHECK};
		bool *opt [3] = {&_options._isProjectPanel_1, &_options._isProjectPanel_2, &_options._isProjectPanel_3};
		bool *hst [3] = {&findHistory._isFifProjectPanel_1, &findHistory._isFifProjectPanel_2, &findHistory._isFifProjectPanel_3};
		bool enable = false;
		for (int i = 0; i < 3; i++)
		{
			UINT s = GetMenuState (hMenu, idm [i], MF_BYCOMMAND);
			if (s != ((UINT)-1))
			{
				if (s & MF_CHECKED)
				{
					enableFindDlgItem (idd [i], true);
					if (BST_CHECKED == ::SendDlgItemMessage(_hSelf, idd [i], BM_GETCHECK, 0, 0))
						enable = true;
				}
				else
				{
					*opt [i] = 0;
					*hst [i] = 0;
					::SendDlgItemMessage(_hSelf, idd [i], BM_SETCHECK, 0, 0);
					enableFindDlgItem (idd [i], false);
				}
			}
		}
		enableFindDlgItem (IDD_FINDINFILES_FIND_BUTTON, enable || (_currentStatus != FINDINPROJECTS_DLG));
		enableFindDlgItem (IDD_FINDINFILES_REPLACEINPROJECTS, enable);
	}
}

void FindReplaceDlg::setProjectCheckmarks(FindHistory *findHistory, int msk)
{
	_options._isProjectPanel_1 = (msk & 1) ? true : false;
	_options._isProjectPanel_2 = (msk & 2) ? true : false;
	_options._isProjectPanel_3 = (msk & 4) ? true : false;
	FindHistory *fh = findHistory;
	if (! fh)
	{
		NppParameters& nppParams = NppParameters::getInstance();
		fh = & nppParams.getFindHistory();
	}

	if (fh)
	{
		fh->_isFifProjectPanel_1 = _options._isProjectPanel_1;
		fh->_isFifProjectPanel_2 = _options._isProjectPanel_2;
		fh->_isFifProjectPanel_3 = _options._isProjectPanel_3;
	}
	::SendDlgItemMessage(_hSelf, IDD_FINDINFILES_PROJECT1_CHECK, BM_SETCHECK, _options._isProjectPanel_1, 0);
	::SendDlgItemMessage(_hSelf, IDD_FINDINFILES_PROJECT2_CHECK, BM_SETCHECK, _options._isProjectPanel_2, 0);
	::SendDlgItemMessage(_hSelf, IDD_FINDINFILES_PROJECT3_CHECK, BM_SETCHECK, _options._isProjectPanel_3, 0);

	if (_currentStatus == FINDINPROJECTS_DLG)
	{
		enableFindDlgItem (IDD_FINDINFILES_FIND_BUTTON, msk != 0);
		enableFindDlgItem (IDD_FINDINFILES_REPLACEINPROJECTS, msk != 0);
	}
}

void FindReplaceDlg::initOptionsFromDlg()
{
	_options._isWholeWord = isCheckedOrNot(IDWHOLEWORD);
	_options._isMatchCase = isCheckedOrNot(IDMATCHCASE);
	_options._searchType = isCheckedOrNot(IDREGEXP)?FindRegex:isCheckedOrNot(IDEXTENDED)?FindExtended:FindNormal;
	_options._isWrapAround = isCheckedOrNot(IDWRAP);
	_options._isInSelection = isCheckedOrNot(IDC_IN_SELECTION_CHECK);

	_options._dotMatchesNewline = isCheckedOrNot(IDREDOTMATCHNL);
	_options._doPurge = isCheckedOrNot(IDC_PURGE_CHECK);
	_options._doMarkLine = isCheckedOrNot(IDC_MARKLINE_CHECK);

	_options._whichDirection = isCheckedOrNot(IDC_BACKWARDDIRECTION) ? DIR_UP : DIR_DOWN;

	_options._isRecursive = isCheckedOrNot(IDD_FINDINFILES_RECURSIVE_CHECK);
	_options._isInHiddenDir = isCheckedOrNot(IDD_FINDINFILES_INHIDDENDIR_CHECK);
	_options._isProjectPanel_1 =  isCheckedOrNot(IDD_FINDINFILES_PROJECT1_CHECK);
	_options._isProjectPanel_2 =  isCheckedOrNot(IDD_FINDINFILES_PROJECT2_CHECK);
	_options._isProjectPanel_3 =  isCheckedOrNot(IDD_FINDINFILES_PROJECT3_CHECK);
}

void FindInFinderDlg::doDialog(Finder *launcher, bool isRTL)
{
	_pFinder2Search = launcher;
	if (isRTL)
	{
		DLGTEMPLATE *pMyDlgTemplate = NULL;
		HGLOBAL hMyDlgTemplate = makeRTLResource(IDD_FINDINFINDER_DLG, &pMyDlgTemplate);
		::DialogBoxIndirectParam(_hInst, pMyDlgTemplate, _hParent, dlgProc, reinterpret_cast<LPARAM>(this));
		::GlobalFree(hMyDlgTemplate);
	}
	else
		::DialogBoxParam(_hInst, MAKEINTRESOURCE(IDD_FINDINFINDER_DLG), _hParent, dlgProc, reinterpret_cast<LPARAM>(this));

}

void FindReplaceDlg::doDialog(DIALOG_TYPE whichType, bool isRTL, bool toShow)
{
	if (!isCreated())
	{
		_isRTL = isRTL;
		create(IDD_FIND_REPLACE_DLG, isRTL, true, toShow);
	}

	if (whichType == FINDINFILES_DLG)
		enableFindInFilesFunc();
	else if (whichType == FINDINPROJECTS_DLG)
		enableFindInProjectsFunc();
	else if (whichType == MARK_DLG)
		enableMarkFunc();
	else
		enableReplaceFunc(whichType == REPLACE_DLG);

	::SetFocus(toShow ? ::GetDlgItem(_hSelf, IDFINDWHAT) : (*_ppEditView)->getHSelf());
	display(toShow, true);
}

LRESULT FAR PASCAL FindReplaceDlg::finderProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	if (message == WM_KEYDOWN && (wParam == VK_DELETE || wParam == VK_RETURN || wParam == VK_ESCAPE))
	{
		ScintillaEditView *pScint = (ScintillaEditView *)(::GetWindowLongPtr(hwnd, GWLP_USERDATA));
		Finder *pFinder = (Finder *)(::GetWindowLongPtr(pScint->getHParent(), GWLP_USERDATA));
		if (wParam == VK_RETURN)
		{
			std::pair<intptr_t, intptr_t> newPos = pFinder->gotoFoundLine();

			auto currentPos = pFinder->_scintView.execute(SCI_GETCURRENTPOS);
			intptr_t lno = pFinder->_scintView.execute(SCI_LINEFROMPOSITION, currentPos);
			intptr_t lineStartAbsPos = pFinder->_scintView.execute(SCI_POSITIONFROMLINE, lno);
			intptr_t lineEndAbsPos = pFinder->_scintView.execute(SCI_GETLINEENDPOSITION, lno);

			intptr_t begin = newPos.first + lineStartAbsPos;
			intptr_t end = newPos.second + lineStartAbsPos;

			if (end > lineEndAbsPos)
				end = lineEndAbsPos;

			pFinder->_scintView.execute(SCI_SETSEL, begin, end);
			pFinder->_scintView.execute(SCI_SCROLLRANGE, begin, end);
		}
		else if (wParam == VK_ESCAPE)
			pFinder->display(false);
		else // VK_DELETE
			pFinder->deleteResult();
		return 0;
	}
	else
		// Call default (original) window procedure
		return CallWindowProc(originalFinderProc, hwnd, message, wParam, lParam);
}

LRESULT FAR PASCAL FindReplaceDlg::comboEditProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HWND hwndCombo = reinterpret_cast<HWND>(GetWindowLongPtr(hwnd, GWLP_USERDATA));

	bool isDropped = ::SendMessage(hwndCombo, CB_GETDROPPEDSTATE, 0, 0) != 0;

	static wchar_t draftString[FINDREPLACE_MAXLENGTH]{};

	if (isDropped && (message == WM_KEYDOWN) && (wParam == VK_DELETE))
	{
		auto curSel = ::SendMessage(hwndCombo, CB_GETCURSEL, 0, 0);
		if (curSel != CB_ERR)
		{
			auto itemsRemaining = ::SendMessage(hwndCombo, CB_DELETESTRING, curSel, 0);
			// if we close the dropdown and reopen it, it will be correctly-sized for remaining items
			::SendMessage(hwndCombo, CB_SHOWDROPDOWN, FALSE, 0);
			if (itemsRemaining > 0)
			{
				if (itemsRemaining == curSel)
				{
					--curSel;
				}
				::SendMessage(hwndCombo, CB_SETCURSEL, curSel, 0);
				::SendMessage(hwndCombo, CB_SHOWDROPDOWN, TRUE, 0);
			}
			return 0;
		}
	}
	else if (message == WM_CHAR && wParam == 0x7F) // ASCII "DEL" (Ctrl+Backspace)
	{
		delLeftWordInEdit(hwnd);
		return 0;
	}
	else if (message == WM_SETFOCUS)
	{
		draftString[0] = '\0';
	}
	else if ((message == WM_KEYDOWN) && (wParam == VK_DOWN) && (::SendMessage(hwndCombo, CB_GETCURSEL, 0, 0) == CB_ERR))
	{
		// down key on unselected combobox item -> store current edit text as draft
		::SendMessage(hwndCombo, WM_GETTEXT, FINDREPLACE_MAXLENGTH - 1, reinterpret_cast<LPARAM>(draftString));
	}
	else if ((message == WM_KEYDOWN) && (wParam == VK_UP) && (::SendMessage(hwndCombo, CB_GETCURSEL, 0, 0) == CB_ERR))
	{
		// up key on unselected combobox item -> no change but select current edit text
		::SendMessage(hwndCombo, CB_SETEDITSEL, 0, MAKELPARAM(0, -1));
		return 0;
	}
	else if ((message == WM_KEYDOWN) && (wParam == VK_UP) && (::SendMessage(hwndCombo, CB_GETCURSEL, 0, 0) == 0) && std::wcslen(draftString) > 0)
	{
		// up key on top selected combobox item -> restore draft to edit text
		::SendMessage(hwndCombo, CB_SETCURSEL, WPARAM(-1), 0);
		::SendMessage(hwndCombo, WM_SETTEXT, FINDREPLACE_MAXLENGTH - 1, reinterpret_cast<LPARAM>(draftString));
		::SendMessage(hwndCombo, CB_SETEDITSEL, 0, MAKELPARAM(0, -1));
		return 0;

	}
	return CallWindowProc(originalComboEditProc, hwnd, message, wParam, lParam);
}

void FindReplaceDlg::hideOrShowCtrl4reduceOrNormalMode(DIALOG_TYPE dlgT)
{
	bool isLessModeOn = NppParameters::getInstance().getNppGUI()._findWindowLessMode;
	if (dlgT == FIND_DLG)
	{
		for (int id : _reduce2hide_find)
			::ShowWindow(::GetDlgItem(_hSelf, id), isLessModeOn ? SW_HIDE : SW_SHOW);
	}
	else if (dlgT == REPLACE_DLG)
	{
		for (int id : _reduce2hide_findReplace)
			::ShowWindow(::GetDlgItem(_hSelf, id), isLessModeOn ? SW_HIDE : SW_SHOW);
	}
	else if (dlgT == FINDINFILES_DLG)
	{
		for (int id : _reduce2hide_fif)
			::ShowWindow(::GetDlgItem(_hSelf, id), isLessModeOn ? SW_HIDE : SW_SHOW);
	}
	else if (dlgT == FINDINPROJECTS_DLG)
	{
		for (int id : _reduce2hide_fip)
			::ShowWindow(::GetDlgItem(_hSelf, id), isLessModeOn ? SW_HIDE : SW_SHOW);
	}
	else // MARK_DLG
	{
		for (int id : _reduce2hide_mark)
			::ShowWindow(::GetDlgItem(_hSelf, id), isLessModeOn ? SW_HIDE : SW_SHOW);
	}
}

void FindReplaceDlg::enableFindInFilesFunc()
{
	enableFindInFilesControls(true, false);
	_currentStatus = FINDINFILES_DLG;
	gotoCorrectTab();
	::MoveWindow(::GetDlgItem(_hSelf, IDCANCEL), _findInFilesClosePos.left + _deltaWidth, _findInFilesClosePos.top, _findInFilesClosePos.right, _findInFilesClosePos.bottom, TRUE);
	TCHAR label[MAX_PATH]{};
	_tab.getCurrentTitle(label, MAX_PATH);
	::SetWindowText(_hSelf, label);
	setDefaultButton(IDD_FINDINFILES_FIND_BUTTON);
	enableFindDlgItem (IDD_FINDINFILES_FIND_BUTTON, true);
	hideOrShowCtrl4reduceOrNormalMode(_currentStatus);
}

void FindReplaceDlg::enableFindInProjectsFunc()
{
	enableFindInFilesControls(true, true);
	_currentStatus = FINDINPROJECTS_DLG;
	gotoCorrectTab();
	::MoveWindow(::GetDlgItem(_hSelf, IDCANCEL), _findInFilesClosePos.left + _deltaWidth, _findInFilesClosePos.top, _findInFilesClosePos.right, _findInFilesClosePos.bottom, TRUE);
	TCHAR label[MAX_PATH]{};
	_tab.getCurrentTitle(label, MAX_PATH);
	::SetWindowText(_hSelf, label);
	setDefaultButton(IDD_FINDINFILES_FIND_BUTTON);
	bool enable = _options._isProjectPanel_1 || _options._isProjectPanel_2 || _options._isProjectPanel_3;
	enableFindDlgItem (IDD_FINDINFILES_FIND_BUTTON, enable);
	enableFindDlgItem (IDD_FINDINFILES_REPLACEINPROJECTS, enable);
	hideOrShowCtrl4reduceOrNormalMode(_currentStatus);
}

void FindReplaceDlg::enableMarkFunc()
{
	enableFindInFilesControls(false, false);
	enableMarkAllControls(true);

	// Replace controls to hide
	showFindDlgItem(ID_STATICTEXT_REPLACE, false);
	showFindDlgItem(IDREPLACE, false);
	showFindDlgItem(IDREPLACEWITH, false);
	showFindDlgItem(IDD_FINDREPLACE_SWAP_BUTTON, false);
	showFindDlgItem(IDREPLACEALL, false);
	showFindDlgItem(IDC_REPLACE_OPENEDFILES, false);
	showFindDlgItem(IDC_REPLACEINSELECTION, false);

	// find controls to hide
	showFindDlgItem(IDC_FINDALL_OPENEDFILES, false);
	showFindDlgItem(IDCCOUNTALL, false);
	showFindDlgItem(IDC_FINDALL_CURRENTFILE, false);
	showFindDlgItem(IDOK, false);
	showFindDlgItem(IDC_2_BUTTONS_MODE, false);
	showFindDlgItem(IDC_FINDPREV, false);
	showFindDlgItem(IDC_FINDNEXT, false);

	_currentStatus = MARK_DLG;
	gotoCorrectTab();
	::MoveWindow(::GetDlgItem(_hSelf, IDCANCEL), _findInFilesClosePos.left + _deltaWidth, _findInFilesClosePos.top, _findInFilesClosePos.right, _findInFilesClosePos.bottom, TRUE);
	::MoveWindow(::GetDlgItem(_hSelf, IDC_IN_SELECTION_CHECK), _replaceInSelCheckPos.left + _deltaWidth, _replaceInSelCheckPos.top, _replaceInSelCheckPos.right, _replaceInSelCheckPos.bottom, TRUE);
	::MoveWindow(::GetDlgItem(_hSelf, IDC_REPLACEINSELECTION), _replaceInSelFramePos.left + _deltaWidth, _replaceInSelFramePos.top, _replaceInSelFramePos.right, _replaceInSelFramePos.bottom, TRUE);
	::MoveWindow(::GetDlgItem(_hSelf, IDCANCEL), _markClosePos.left + _deltaWidth, _markClosePos.top, _markClosePos.right, _markClosePos.bottom, TRUE);

	TCHAR label[MAX_PATH]{};
	_tab.getCurrentTitle(label, MAX_PATH);
	::SetWindowText(_hSelf, label);
	setDefaultButton(IDCMARKALL);

	hideOrShowCtrl4reduceOrNormalMode(_currentStatus);
}

void FindReplaceDlg::combo2ExtendedMode(int comboID)
{
	HWND hFindCombo = ::GetDlgItem(_hSelf, comboID);
	if (!hFindCombo) return;

	generic_string str2transform = getTextFromCombo(hFindCombo);

	// Count the number of character '\n' and '\r'
	size_t nbEOL = 0;
	size_t str2transformLen = lstrlen(str2transform.c_str());
	for (size_t i = 0 ; i < str2transformLen ; ++i)
	{
		if (str2transform[i] == '\r' || str2transform[i] == '\n')
			++nbEOL;
	}

	if (nbEOL)
	{
		TCHAR * newBuffer = new TCHAR[str2transformLen + nbEOL*2 + 1];
		int j = 0;
		for (size_t i = 0 ; i < str2transformLen ; ++i)
		{
			if (str2transform[i] == '\r')
			{
				newBuffer[j++] = '\\';
				newBuffer[j++] = 'r';
			}
			else if (str2transform[i] == '\n')
			{
				newBuffer[j++] = '\\';
				newBuffer[j++] = 'n';
			}
			else
			{
				newBuffer[j++] = str2transform[i];
			}
		}
		newBuffer[j++] = '\0';
		setSearchText(newBuffer);

		_options._searchType = FindExtended;
		::SendDlgItemMessage(_hSelf, IDNORMAL, BM_SETCHECK, FALSE, 0);
		::SendDlgItemMessage(_hSelf, IDEXTENDED, BM_SETCHECK, TRUE, 0);
		::SendDlgItemMessage(_hSelf, IDREGEXP, BM_SETCHECK, FALSE, 0);

		delete [] newBuffer;
    }
}

void FindReplaceDlg::drawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
	//printStr(TEXT("OK"));
	COLORREF fgColor = RGB(0, 0, 0); // black by default
	PCTSTR ptStr =(PCTSTR)lpDrawItemStruct->itemData;

	if (_statusbarFindStatus == FSNotFound)
	{
		fgColor = RGB(0xFF, 00, 00); // red
	}
	else if (_statusbarFindStatus == FSMessage)
	{
		fgColor = RGB(0, 0, 0xFF); // blue
	}
	else if (_statusbarFindStatus == FSTopReached || _statusbarFindStatus == FSEndReached)
	{
		fgColor = RGB(0, 166, 0); // green
	}
	else if (_statusbarFindStatus == FSNoMessage)
	{
		ptStr = TEXT("");
	}

	if (NppDarkMode::isEnabled())
	{
		fgColor = NppDarkMode::getTextColor();

		if (_statusbarFindStatus == FSNotFound)
		{
			fgColor = RGB(0xFF, 0x50, 0x50); // red
		}
		else if (_statusbarFindStatus == FSMessage)
		{
			fgColor = RGB(0x70, 0x70, 0xFF); // blue
		}
		else if (_statusbarFindStatus == FSTopReached || _statusbarFindStatus == FSEndReached)
		{
			fgColor = RGB(0x50, 0xFF, 0x50); // green
		}
	}

	SetTextColor(lpDrawItemStruct->hDC, fgColor);

	COLORREF bgColor;
	if (NppDarkMode::isEnabled())
	{
		bgColor = NppDarkMode::getBackgroundColor();
	}
	else
	{
		bgColor = getCtrlBgColor(_statusBar.getHSelf());
	}
	::SetBkColor(lpDrawItemStruct->hDC, bgColor);
	RECT rect{};
	_statusBar.getClientRect(rect);

	if (NppDarkMode::isEnabled())
	{
		rect.left += 2;
	}

	::DrawText(lpDrawItemStruct->hDC, ptStr, lstrlen(ptStr), &rect, DT_SINGLELINE | DT_VCENTER | DT_LEFT);

	if (_statusbarTooltipMsg.length() == 0) return;

	SIZE size{};
	::GetTextExtentPoint32(lpDrawItemStruct->hDC, ptStr, lstrlen(ptStr), &size);
	int s = (rect.bottom - rect.top) & 0x70; // limit s to available icon sizes and avoid uneven scalings
	if (s > 0)
	{
		if (_statusbarTooltipIcon && (_statusbarTooltipIconSize != s))
		{
			DestroyIcon (_statusbarTooltipIcon);
			_statusbarTooltipIcon = nullptr;
		}

		if (!_statusbarTooltipIcon)
			_statusbarTooltipIcon = (HICON)::LoadImage(_hInst, MAKEINTRESOURCE(IDI_GET_INFO_FROM_TOOLTIP), IMAGE_ICON, s, s, 0);

		if (_statusbarTooltipIcon)
		{
			_statusbarTooltipIconSize = s;
			rect.left = rect.left + size.cx + s / 2;
			rect.top  = (rect.top + rect.bottom - s) / 2;
			DrawIconEx (lpDrawItemStruct->hDC, rect.left, rect.top, _statusbarTooltipIcon, s, s, 0, NULL, DI_NORMAL);
			if (!_statusbarTooltipWnd)
			{
				rect.right = rect.left + s;
				rect.bottom = rect.top + s;
				_statusbarTooltipWnd = CreateToolTipRect(1, _statusBar.getHSelf(), _hInst, const_cast<PTSTR>(_statusbarTooltipMsg.c_str()), rect);

				NppDarkMode::setDarkTooltips(_statusbarTooltipWnd, NppDarkMode::ToolTipsType::tooltip);
			}
		}
		else
		{
			_statusbarTooltipIconSize = 0;
		}
	}
}

bool FindReplaceDlg::replaceInFilesConfirmCheck(generic_string directory, generic_string fileTypes)
{
	bool confirmed = false;

	NativeLangSpeaker* pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();

	generic_string title = pNativeSpeaker->getLocalizedStrFromID("replace-in-files-confirm-title", TEXT("Are you sure?"));

	generic_string msg = pNativeSpeaker->getLocalizedStrFromID("replace-in-files-confirm-directory", TEXT("Are you sure you want to replace all occurrences in :"));
	msg += TEXT("\r\r");
	msg += directory;

	msg += TEXT("\r\r");

	generic_string msg2 = pNativeSpeaker->getLocalizedStrFromID("replace-in-files-confirm-filetype", TEXT("For file type :"));
	msg2 += TEXT("\r\r");
	msg2 += fileTypes[0] ? fileTypes : TEXT("*.*");

	msg += msg2;

	int res = ::MessageBox(NULL, msg.c_str(), title.c_str(), MB_OKCANCEL | MB_DEFBUTTON2 | MB_TASKMODAL);

	if (res == IDOK)
	{
		confirmed = true;
	}

	return confirmed;
}

bool FindReplaceDlg::replaceInProjectsConfirmCheck()
{
	bool confirmed = false;

	NativeLangSpeaker* pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();

	generic_string title = pNativeSpeaker->getLocalizedStrFromID("replace-in-projects-confirm-title", TEXT("Are you sure?"));

	generic_string msg = pNativeSpeaker->getLocalizedStrFromID("replace-in-projects-confirm-message", TEXT("Do you want to replace all occurrences in all documents in the selected Project Panel(s)?"));
	int res = ::MessageBox(NULL, msg.c_str(), title.c_str(), MB_OKCANCEL | MB_DEFBUTTON2 | MB_TASKMODAL);

	if (res == IDOK)
	{
		confirmed = true;
	}

	return confirmed;
}

bool FindReplaceDlg::replaceInOpenDocsConfirmCheck(void)
{
	bool confirmed = false;

	NativeLangSpeaker* pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();
	generic_string title = pNativeSpeaker->getLocalizedStrFromID("replace-in-open-docs-confirm-title", TEXT("Are you sure?"));
	generic_string msg = pNativeSpeaker->getLocalizedStrFromID("replace-in-open-docs-confirm-message", TEXT("Are you sure you want to replace all occurrences in all open documents?"));

	int res = ::MessageBox(NULL, msg.c_str(), title.c_str(), MB_OKCANCEL | MB_DEFBUTTON2 | MB_TASKMODAL);

	if (res == IDOK)
	{
		confirmed = true;
	}

	return confirmed;
}

generic_string Finder::getHitsString(int count) const
{
	NativeLangSpeaker *pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();
	generic_string text = pNativeSpeaker->getLocalizedStrFromID("find-result-hits", TEXT(""));

	if (text.empty())
	{
		if (count == 1)
		{
			text = TEXT("(1 hit)");
		}
		else
		{
			text = TEXT("(");
			text += std::to_wstring(count);
			text += TEXT(" hits)");
		}
	}
	else
	{
		text = stringReplace(text, TEXT("$INT_REPLACE$"), std::to_wstring(count));
	}

	return text;
}

void Finder::addSearchLine(const TCHAR *searchName)
{
	NativeLangSpeaker *pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();
	generic_string str = pNativeSpeaker->getLocalizedStrFromID("find-result-title", TEXT("Search"));
	str += TEXT(" \"");
	str += searchName;
	str += TEXT("\" \r\n");

	setFinderReadOnly(false);
	_scintView.addGenericText(str.c_str());
	setFinderReadOnly(true);
	_lastSearchHeaderPos = _scintView.execute(SCI_GETCURRENTPOS) - 2;

	_pMainFoundInfos->push_back(EmptyFoundInfo);
	_pMainMarkings->push_back(EmptySearchResultMarking);
	_previousLineNumber = -1;
}

void Finder::addFileNameTitle(const TCHAR * fileName)
{
	generic_string str = TEXT("  ");
	str += fileName;
	str += TEXT("\r\n");

	setFinderReadOnly(false);
	_scintView.addGenericText(str.c_str());
	setFinderReadOnly(true);
	_lastFileHeaderPos = _scintView.execute(SCI_GETCURRENTPOS) - 2;

	_pMainFoundInfos->push_back(EmptyFoundInfo);
	_pMainMarkings->push_back(EmptySearchResultMarking);
	_previousLineNumber = -1;
}

void Finder::addFileHitCount(int count)
{
	wstring text = TEXT(" ");
	text += getHitsString(count);
	setFinderReadOnly(false);
	_scintView.insertGenericTextFrom(_lastFileHeaderPos, text.c_str());
	setFinderReadOnly(true);
	++_nbFoundFiles;
}

void Finder::addSearchHitCount(int count, int countSearched, bool isMatchLines, bool searchedEntireNotSelection)
{
	generic_string nbResStr = std::to_wstring(count);
	generic_string nbFoundFilesStr = std::to_wstring(_nbFoundFiles);
	generic_string nbSearchedFilesStr = std::to_wstring(countSearched);

	NativeLangSpeaker* pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();

	generic_string text = pNativeSpeaker->getLocalizedStrFromID(
		searchedEntireNotSelection ? "find-result-title-info" : "find-result-title-info-selections",
		TEXT(""));

	if (text.empty())
	{
		// create default output: "(1 hit in 1 file of 1 searched)"
		//  variation             "(1 hit in 2 files of 2 searched)"
		//  variation             "(2 hits in 1 file of 3 searched)"
		//  variation             "(0 hits in 0 files of 1 searched)"
		// selection variations:  "(1 hit in 1 selection of 1 searched)"     " (0 hits in 0 selections of 1 searched)"
		// line filter variation: "(1 hit in 1 file of 1 searched) - Line Filter Mode: only display the filtered results"

		generic_string hitsIn = count == 1 ? TEXT("hit") : TEXT("hits");

		generic_string fileOrSelection = searchedEntireNotSelection ? TEXT("file") : TEXT("selection");;
		if (_nbFoundFiles != 1)
		{
			fileOrSelection += TEXT("s");
		}

		text = TEXT("(") + nbResStr + TEXT(" ") + hitsIn + TEXT(" in ") + nbFoundFilesStr + TEXT(" ") +
			fileOrSelection + TEXT(" of ") + nbSearchedFilesStr + TEXT(" searched") TEXT(")");
	}
	else
	{
		text = stringReplace(text, TEXT("$INT_REPLACE1$"), nbResStr);
		text = stringReplace(text, TEXT("$INT_REPLACE2$"), nbFoundFilesStr);
		text = stringReplace(text, TEXT("$INT_REPLACE3$"), nbSearchedFilesStr);
	}

	if (isMatchLines)
	{
		generic_string lineFilterModeInfo = pNativeSpeaker->getLocalizedStrFromID("find-result-title-info-extra", TEXT(" - Line Filter Mode: only display the filtered results"));
		text += lineFilterModeInfo;
	}

	setFinderReadOnly(false);
	_scintView.insertGenericTextFrom(_lastSearchHeaderPos, text.c_str());
	setFinderReadOnly(true);
}

const char* Finder::foundLine(FoundInfo fi, SearchResultMarkingLine miLine, const TCHAR* foundline, size_t totalLineNumber)
{
	bool isRepeatedLine = false;

	NppParameters& nppParam = NppParameters::getInstance();
	NppGUI& nppGUI = nppParam.getNppGUI();

	if (nppGUI._finderShowOnlyOneEntryPerFoundLine)
	{
		if (_previousLineNumber == -1)
		{
			_previousLineNumber = fi._lineNumber;
		}
		else if (_previousLineNumber == static_cast<intptr_t>(fi._lineNumber))
		{
			isRepeatedLine = true;
		}
		else // previousLine != fi._lineNumber
		{
			_previousLineNumber = fi._lineNumber;
		}
	}

	generic_string headerStr = TEXT("\t");
	headerStr += _prefixLineStr;
	headerStr += TEXT(" ");

	size_t totalLineNumberDigit = static_cast<size_t>(nbDigitsFromNbLines(totalLineNumber) + 1);
	size_t currentLineNumberDigit = static_cast<size_t>(nbDigitsFromNbLines(fi._lineNumber) + 1);

	generic_string lineNumberStr = TEXT("");
	lineNumberStr.append(totalLineNumberDigit - currentLineNumberDigit, ' ');
	lineNumberStr.append(std::to_wstring(fi._lineNumber));
	headerStr += lineNumberStr;
	headerStr += TEXT(": ");

	miLine._segmentPostions[0].first += headerStr.length();
	miLine._segmentPostions[0].second += headerStr.length();
	headerStr += foundline;
	WcharMbcsConvertor& wmc = WcharMbcsConvertor::getInstance();
	const char* text2AddUtf8 = wmc.wchar2char(headerStr.c_str(), SC_CP_UTF8, &miLine._segmentPostions[0].first, &miLine._segmentPostions[0].second); // certainly utf8 here

	if (isRepeatedLine) // if current line is the repeated line of previous one, and settings make per found line show once in the result even there are several found occurences in the same line
	{
		// Add start and end markers into the previous line's info for colourizing 
		_pMainMarkings->back()._segmentPostions.push_back(std::pair<intptr_t, intptr_t>(miLine._segmentPostions[0].first, miLine._segmentPostions[0].second));
		_pMainFoundInfos->back()._ranges.push_back(fi._ranges.back());
		return "";
	}
	else // default mode: allow same found line has several entries in search result if the searched occurrence is matched several times in the same line
	{
		_pMainFoundInfos->push_back(fi);

		size_t len = strlen(text2AddUtf8);
		if (len >= SC_SEARCHRESULT_LINEBUFFERMAXLENGTH)
		{
			const char* endOfLongLine = " ...\r\n"; // perfectly Utf8-encoded already
			size_t lenEndOfLongLine = strlen(endOfLongLine);
			size_t cut = SC_SEARCHRESULT_LINEBUFFERMAXLENGTH - lenEndOfLongLine - 1;

			while ((cut > 0) && (!Utf8::isValid(&text2AddUtf8[cut], (int)(len - cut))))
				cut--;

			memcpy((void*)&text2AddUtf8[cut], endOfLongLine, lenEndOfLongLine + 1);
			len = cut + lenEndOfLongLine;
		}

		_pMainMarkings->push_back(miLine);

		return text2AddUtf8;
	}
}

void Finder::removeAll()
{
	_pMainFoundInfos->clear();
	_pMainMarkings->clear();
	setFinderReadOnly(false);
	_scintView.execute(SCI_CLEARALL);
	setFinderReadOnly(true);
}

void Finder::openAll()
{
	for (auto&& path : getResultFilePaths())
	{
		::SendMessage(_hParent, WM_DOOPEN, 0, reinterpret_cast<LPARAM>(path.c_str()));
	}
}

void Finder::copyPathnames()
{
	generic_string toClipboard;
	for (auto&& path : getResultFilePaths())
	{
		toClipboard += path + TEXT("\r\n");
	}
	if (!toClipboard.empty())
	{
		if (!str2Clipboard(toClipboard, _hSelf))
		{
			assert(false);
			::MessageBox(NULL, TEXT("Error placing pathnames into clipboard."), TEXT("Notepad++"), MB_ICONINFORMATION);
		}
	}
}

void Finder::wrapLongLinesToggle()
{
	_longLinesAreWrapped = !_longLinesAreWrapped;
	_scintView.wrap(_longLinesAreWrapped);

	if (!_canBeVolatiled)
	{
		// only remember this setting from the original finder
		NppParameters& nppParam = NppParameters::getInstance();
		NppGUI& nppGUI = nppParam.getNppGUI();
		nppGUI._finderLinesAreCurrentlyWrapped = _longLinesAreWrapped;
	}
}

void Finder::purgeToggle()
{
	_purgeBeforeEverySearch = !_purgeBeforeEverySearch;

	if (!_canBeVolatiled)
	{
		// only remember this setting from the original finder
		NppParameters& nppParam = NppParameters::getInstance();
		NppGUI& nppGUI = nppParam.getNppGUI();
		nppGUI._finderPurgeBeforeEverySearch = _purgeBeforeEverySearch;
	}
}

bool Finder::isLineActualSearchResult(const generic_string & s) const
{
	// actual-search-result lines are the only type that start with a tab character
	// sample: "\tLine 123: xxxxxxHITxxxxxx"
	return (s.find(TEXT("\t")) == 0);
}

generic_string & Finder::prepareStringForClipboard(generic_string & s) const
{
	// Input: a string like "\tLine 3: search result".
	// Output: "search result"
	s = stringReplace(s, TEXT("\r"), TEXT(""));
	s = stringReplace(s, TEXT("\n"), TEXT(""));
	const auto firstColon = s.find(TEXT(':'));
	if (firstColon == std::string::npos)
	{
		// Should never happen.
		assert(false);
		return s;
	}
	else
	{
		// Plus 2 in order to deal with ": ".
		s = s.substr(2 + firstColon);
		return s;
	}
}

void Finder::copy()
{
	if (_scintView.execute(SCI_GETSELECTIONS) > 1) // multi-selection
	{
		// don't do anything if user has made a column/rectangular selection
		return;
	}

	size_t fromLine = 0, toLine = 0;
	{
		const pair<size_t, size_t> lineRange = _scintView.getSelectionLinesRange();
		fromLine = lineRange.first;
		toLine = lineRange.second;

		// Abuse fold levels to find out which lines to copy to clipboard.
		// We get the current line and then the next line which has a smaller fold level (SCI_GETLASTCHILD).
		// Then we loop all lines between them and determine which actually contain search results.
		const int selectedLineFoldLevel = _scintView.execute(SCI_GETFOLDLEVEL, fromLine) & SC_FOLDLEVELNUMBERMASK;
		toLine = _scintView.execute(SCI_GETLASTCHILD, toLine, selectedLineFoldLevel);
	}

	std::vector<generic_string> lines;
	generic_string previousResultLineStr(TEXT(""));
	for (size_t line = fromLine; line <= toLine; ++line)
	{
		generic_string lineStr = _scintView.getLine(line);
		if (isLineActualSearchResult(lineStr))
		{
			if (lineStr != previousResultLineStr)
			{
				previousResultLineStr = lineStr;
				lines.push_back(prepareStringForClipboard(lineStr));
			}
		}
	}
	generic_string toClipboard;
	stringJoin(lines, TEXT("\r\n"), toClipboard);
	toClipboard += TEXT("\r\n");
	if (!toClipboard.empty())
	{
		if (!str2Clipboard(toClipboard, _hSelf))
		{
			assert(false);
			::MessageBox(NULL, TEXT("Error placing text in clipboard."), TEXT("Notepad++"), MB_ICONINFORMATION);
		}
	}
}

void Finder::beginNewFilesSearch()
{
	NativeLangSpeaker* pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();
	_prefixLineStr = pNativeSpeaker->getLocalizedStrFromID("find-result-line-prefix", TEXT("Line"));

	// Use SCI_SETSEL(0, 0) instead of SCI_SETCURRENTPOS(0) to workaround
	// an eventual regression or a change of behaviour in Scintilla 4.4.6
	// ref: https://github.com/notepad-plus-plus/notepad-plus-plus/issues/9595#issuecomment-789824579
	//
	_scintView.execute(SCI_SETSEL, 0, 0);
	//_scintView.execute(SCI_SETCURRENTPOS, 0);

	_pMainFoundInfos = _pMainFoundInfos == &_foundInfos1 ? &_foundInfos2 : &_foundInfos1;
	_pMainMarkings = _pMainMarkings == &_markings1 ? &_markings2 : &_markings1;
	_nbFoundFiles = 0;

	// fold all old searches (1st level only)
	_scintView.collapse(searchHeaderLevel - SC_FOLDLEVELBASE, fold_collapse);
}

void Finder::finishFilesSearch(int count, int searchedCount, bool isMatchLines, bool searchedEntireNotSelection)
{
	std::vector<FoundInfo>* _pOldFoundInfos;
	std::vector<SearchResultMarkingLine>* _pOldMarkings;
	_pOldFoundInfos = _pMainFoundInfos == &_foundInfos1 ? &_foundInfos2 : &_foundInfos1;
	_pOldMarkings = _pMainMarkings == &_markings1 ? &_markings2 : &_markings1;

	_pOldFoundInfos->insert(_pOldFoundInfos->begin(), _pMainFoundInfos->begin(), _pMainFoundInfos->end());
	_pOldMarkings->insert(_pOldMarkings->begin(), _pMainMarkings->begin(), _pMainMarkings->end());
	_pMainFoundInfos->clear();
	_pMainMarkings->clear();
	_pMainFoundInfos = _pOldFoundInfos;
	_pMainMarkings = _pOldMarkings;

	_markingsStruct._length = static_cast<long>(_pMainMarkings->size());
	if (_pMainMarkings->size() > 0)
		_markingsStruct._markings = &((*_pMainMarkings)[0]);

	addSearchHitCount(count, searchedCount, isMatchLines, searchedEntireNotSelection);
	_scintView.execute(SCI_SETSEL, 0, 0);

	//SCI_SETILEXER resets the lexer property @MarkingsStruct and then no data could be exchanged with the searchResult lexer
	char ptrword[sizeof(void*) * 2 + 1];
	sprintf(ptrword, "%p", static_cast<void*>(&_markingsStruct));
	_scintView.execute(SCI_SETPROPERTY, reinterpret_cast<WPARAM>("@MarkingsStruct"), reinterpret_cast<LPARAM>(ptrword));

	//previous code: _scintView.execute(SCI_SETILEXER, 0, reinterpret_cast<LPARAM>(CreateLexer("searchResult")));
	_scintView.execute(SCI_SETPROPERTY, reinterpret_cast<WPARAM>("fold"), reinterpret_cast<LPARAM>("1"));

	_previousLineNumber = -1;
}


void Finder::setFinderStyle()
{
	// Set global styles for the finder
	_scintView.performGlobalStyles();

	NppDarkMode::setDarkScrollBar(_scintView.getHSelf());

	// Set current line background color for the finder
	const TCHAR * lexerName = ScintillaEditView::_langNameInfoArray[L_SEARCHRESULT]._langName;
	LexerStyler *pStyler = (NppParameters::getInstance().getLStylerArray()).getLexerStylerByName(lexerName);
	if (pStyler)
	{
		const Style * pStyle = pStyler->findByID(SCE_SEARCHRESULT_CURRENT_LINE);
		if (pStyle)
		{
			_scintView.execute(SCI_SETELEMENTCOLOUR, SC_ELEMENT_CARET_LINE_BACK, pStyle->_bgColor);
			_scintView.execute(SCI_SETCARETLINEFRAME, 0);
			_scintView.execute(SCI_SETCARETLINEVISIBLEALWAYS, true);
		}
	}
	_scintView.setSearchResultLexer();

	// Override foreground & background colour by default foreground & background coulour
	StyleArray & stylers = NppParameters::getInstance().getMiscStylerArray();
	Style * pStyleDefault = stylers.findByID(STYLE_DEFAULT);
	if (pStyleDefault)
	{
		_scintView.setStyle(*pStyleDefault);

		GlobalOverride & go = NppParameters::getInstance().getGlobalOverrideStyle();
		if (go.isEnable())
		{
			const Style * pStyleGlobalOverride = stylers.findByName(TEXT("Global override"));
			if (pStyleGlobalOverride)
			{
				if (go.enableFg)
				{
					pStyleDefault->_fgColor = pStyleGlobalOverride->_fgColor;
				}
				if (go.enableBg)
				{
					pStyleDefault->_bgColor = pStyleGlobalOverride->_bgColor;
				}
			}
		}

		_scintView.execute(SCI_STYLESETFORE, SCE_SEARCHRESULT_DEFAULT, pStyleDefault->_fgColor);
		_scintView.execute(SCI_STYLESETBACK, SCE_SEARCHRESULT_DEFAULT, pStyleDefault->_bgColor);
	}

	_scintView.execute(SCI_COLOURISE, 0, -1);

	// finder fold style follows user preference but use box when user selects none
	ScintillaViewParams& svp = (ScintillaViewParams&)NppParameters::getInstance().getSVP();
	_scintView.setMakerStyle(svp._folderStyle == FOLDER_STYLE_NONE ? FOLDER_STYLE_BOX : svp._folderStyle);
}

void Finder::setFinderStyleForNpc(bool onlyColor)
{
	NppParameters& nppParam = NppParameters::getInstance();
	const bool isShown = nppParam.getSVP()._npcShow;
	if (!onlyColor)
	{
		_scintView.showNpc(isShown, true);
	}
	else if (isShown)
	{
		_scintView.setNpcAndCcUniEOL();
	}
}

intptr_t CALLBACK Finder::run_dlgProc(UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
		case WM_COMMAND :
		{
			switch (wParam)
			{
				case NPPM_INTERNAL_FINDINFINDERDLG:
				{
					::SendMessage(_hParent, NPPM_INTERNAL_FINDINFINDERDLG, reinterpret_cast<WPARAM>(this), 0);
					return TRUE;
				}

				case NPPM_INTERNAL_REMOVEFINDER:
				{
					if (_canBeVolatiled)
					{
						::SendMessage(_hParent, NPPM_DMMHIDE, 0, reinterpret_cast<LPARAM>(_hSelf));
						setClosed(true);
					}
					return TRUE;
				}

				case NPPM_INTERNAL_SCINTILLAFINDERCOLLAPSE :
				{
					_scintView.foldAll(fold_collapse);
					return TRUE;
				}

				case NPPM_INTERNAL_SCINTILLAFINDERUNCOLLAPSE :
				{
					_scintView.foldAll(fold_uncollapse);
					return TRUE;
				}

				case NPPM_INTERNAL_SCINTILLAFINDERCOPY :
				{
					copy();
					return TRUE;
				}

				case NPPM_INTERNAL_SCINTILLAFINDERCOPYVERBATIM:
				{
					_scintView.execute(SCI_COPY);

					return TRUE;
				}

				case NPPM_INTERNAL_SCINTILLAFINDERCOPYPATHS:
				{
					copyPathnames();
					return TRUE;
				}

				case NPPM_INTERNAL_SCINTILLAFINDERSELECTALL :
				{
					_scintView.execute(SCI_SELECTALL);
					return TRUE;
				}

				case NPPM_INTERNAL_SCINTILLAFINDERCLEARALL:
				{
					removeAll();
					return TRUE;
				}

				case NPPM_INTERNAL_SCINTILLAFINDEROPENALL:
				{
					openAll();
					return TRUE;
				}

				case NPPM_INTERNAL_SCINTILLAFINDERWRAP:
				{
					wrapLongLinesToggle();
					return TRUE;
				}

				case NPPM_INTERNAL_SCINTILLAFINDERPURGE:
				{
					purgeToggle();
					return TRUE;
				}

				default :
				{
					return FALSE;
				}
			}
		}

		case WM_CONTEXTMENU :
		{
			if (HWND(wParam) == _scintView.getHSelf())
			{
				POINT p;
				::GetCursorPos(&p);
				ContextMenu scintillaContextmenu;
				vector<MenuItemUnit> tmp;

				NativeLangSpeaker *pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();

				generic_string findInFinder = pNativeSpeaker->getLocalizedStrFromID("finder-find-in-finder", TEXT("Find in these search results..."));
				generic_string closeThis = pNativeSpeaker->getLocalizedStrFromID("finder-close-this", TEXT("Close these search results"));
				generic_string collapseAll = pNativeSpeaker->getLocalizedStrFromID("finder-collapse-all", TEXT("Fold all"));
				generic_string uncollapseAll = pNativeSpeaker->getLocalizedStrFromID("finder-uncollapse-all", TEXT("Unfold all"));
				generic_string copyLines = pNativeSpeaker->getLocalizedStrFromID("finder-copy", TEXT("Copy Selected Line(s)"));
				generic_string copyVerbatim = pNativeSpeaker->getLocalizedStrFromID("finder-copy-verbatim", TEXT("Copy"));
				copyVerbatim += TEXT("\tCtrl+C");
				generic_string copyPaths = pNativeSpeaker->getLocalizedStrFromID("finder-copy-paths", TEXT("Copy Pathname(s)"));
				generic_string selectAll = pNativeSpeaker->getLocalizedStrFromID("finder-select-all", TEXT("Select all"));
				selectAll += TEXT("\tCtrl+A");
				generic_string clearAll = pNativeSpeaker->getLocalizedStrFromID("finder-clear-all", TEXT("Clear all"));
				generic_string purgeForEverySearch = pNativeSpeaker->getLocalizedStrFromID("finder-purge-for-every-search", TEXT("Purge for every search"));
				generic_string openAll = pNativeSpeaker->getLocalizedStrFromID("finder-open-all", TEXT("Open all"));
				generic_string wrapLongLines = pNativeSpeaker->getLocalizedStrFromID("finder-wrap-long-lines", TEXT("Word wrap long lines"));

				tmp.push_back(MenuItemUnit(NPPM_INTERNAL_FINDINFINDERDLG, findInFinder));
				if (_canBeVolatiled)
					tmp.push_back(MenuItemUnit(NPPM_INTERNAL_REMOVEFINDER, closeThis));
				tmp.push_back(MenuItemUnit(0, TEXT("Separator")));
				tmp.push_back(MenuItemUnit(NPPM_INTERNAL_SCINTILLAFINDERCOLLAPSE, collapseAll));
				tmp.push_back(MenuItemUnit(NPPM_INTERNAL_SCINTILLAFINDERUNCOLLAPSE, uncollapseAll));
				tmp.push_back(MenuItemUnit(0, TEXT("Separator")));
				tmp.push_back(MenuItemUnit(NPPM_INTERNAL_SCINTILLAFINDERCOPYVERBATIM, copyVerbatim));
				tmp.push_back(MenuItemUnit(NPPM_INTERNAL_SCINTILLAFINDERCOPY, copyLines));
				tmp.push_back(MenuItemUnit(NPPM_INTERNAL_SCINTILLAFINDERCOPYPATHS, copyPaths));
				tmp.push_back(MenuItemUnit(NPPM_INTERNAL_SCINTILLAFINDERSELECTALL, selectAll));
				tmp.push_back(MenuItemUnit(NPPM_INTERNAL_SCINTILLAFINDERCLEARALL, clearAll));
				tmp.push_back(MenuItemUnit(0, TEXT("Separator")));
				tmp.push_back(MenuItemUnit(NPPM_INTERNAL_SCINTILLAFINDEROPENALL, openAll));
				// configuration items go at the bottom:
				tmp.push_back(MenuItemUnit(0, TEXT("Separator")));
				tmp.push_back(MenuItemUnit(NPPM_INTERNAL_SCINTILLAFINDERWRAP, wrapLongLines));
				tmp.push_back(MenuItemUnit(NPPM_INTERNAL_SCINTILLAFINDERPURGE, purgeForEverySearch));

				scintillaContextmenu.create(_hSelf, tmp);

				bool hasSomeSelectedText = _scintView.getSelectedTextCount() > 0;
				scintillaContextmenu.enableItem(NPPM_INTERNAL_SCINTILLAFINDERCOPYVERBATIM, hasSomeSelectedText);

				scintillaContextmenu.enableItem(NPPM_INTERNAL_SCINTILLAFINDERCLEARALL, !_canBeVolatiled);

				scintillaContextmenu.enableItem(NPPM_INTERNAL_SCINTILLAFINDERPURGE, !_canBeVolatiled);
				scintillaContextmenu.checkItem(NPPM_INTERNAL_SCINTILLAFINDERPURGE, _purgeBeforeEverySearch && !_canBeVolatiled);

				scintillaContextmenu.checkItem(NPPM_INTERNAL_SCINTILLAFINDERWRAP, _longLinesAreWrapped);

				::TrackPopupMenu(scintillaContextmenu.getMenuHandle(),
					NppParameters::getInstance().getNativeLangSpeaker()->isRTL() ? TPM_RIGHTALIGN | TPM_LAYOUTRTL : TPM_LEFTALIGN,
					p.x, p.y, 0, _hSelf, NULL);
				return TRUE;
			}
			return ::DefWindowProc(_hSelf, message, wParam, lParam);
		}

		case WM_SIZE :
		{
			RECT rc{};
			getClientRect(rc);
			_scintView.reSizeTo(rc);
			break;
		}

		case WM_NOTIFY:
		{
			notify(reinterpret_cast<SCNotification *>(lParam));
			return FALSE;
		}
		default :
			return DockingDlgInterface::run_dlgProc(message, wParam, lParam);
	}
	return FALSE;
}

void FindIncrementDlg::init(HINSTANCE hInst, HWND hPere, FindReplaceDlg *pFRDlg, bool isRTL)
{
	Window::init(hInst, hPere);
	if (!pFRDlg)
		throw std::runtime_error("FindIncrementDlg::init : Parameter pFRDlg is null");

	_pFRDlg = pFRDlg;
	create(IDD_INCREMENT_FIND, isRTL);
	_isRTL = isRTL;
}

void FindIncrementDlg::destroy()
{
	if (_pRebar)
	{
		_pRebar->removeBand(_rbBand.wID);
		_pRebar = NULL;
	}
}

void FindIncrementDlg::display(bool toShow) const
{
	if (!_pRebar)
	{
		Window::display(toShow);
		return;
	}
	if (toShow)
	{
		::SetFocus(::GetDlgItem(_hSelf, IDC_INCFINDTEXT));
		// select the whole find editor text
		::SendDlgItemMessage(_hSelf, IDC_INCFINDTEXT, EM_SETSEL, 0, -1);
	}
	_pRebar->setIDVisible(_rbBand.wID, toShow);
}

intptr_t CALLBACK FindIncrementDlg::run_dlgProc(UINT message, WPARAM wParam, LPARAM /*lParam*/)
{
	switch (message)
	{
		// Make edit field red if not found
		case WM_CTLCOLOREDIT :
		{
			auto hdc = reinterpret_cast<HDC>(wParam);

			if (NppDarkMode::isEnabled())
			{
				if (FSNotFound != getFindStatus())
				{
					return NppDarkMode::onCtlColorSofter(hdc);
				}
				else // text not found
				{
					return NppDarkMode::onCtlColorError(hdc);
				}
			}

			// if the text not found modify the background color of the editor
			static HBRUSH hBrushBackground = CreateSolidBrush(BCKGRD_COLOR);

			if (FSNotFound != getFindStatus())
				return FALSE; // text found, use the default color

			// text not found
			SetTextColor(hdc, TXT_COLOR);
			SetBkColor(hdc, BCKGRD_COLOR);
			return reinterpret_cast<LRESULT>(hBrushBackground);
		}

		case WM_CTLCOLORDLG:
		case WM_CTLCOLORSTATIC:
		{
			return NppDarkMode::onCtlColorDarker(reinterpret_cast<HDC>(wParam));
		}

		case WM_PRINTCLIENT:
		{
			if (NppDarkMode::isEnabled())
			{
				return TRUE;
			}
			break;
		}

		case NPPM_INTERNAL_REFRESHDARKMODE:
		{
			NppDarkMode::autoThemeChildControls(getHSelf());
			return TRUE;
		}

		case WM_INITDIALOG:
		{
			NppDarkMode::autoSubclassAndThemeChildControls(getHSelf());
			return TRUE;
		}

		case WM_COMMAND :
		{
			bool updateSearch = false;
			bool forward = true;
			bool advance = false;
			bool updateHiLight = false;
			bool updateCase = false;

			switch (LOWORD(wParam))
			{
				case IDCANCEL :
					(*(_pFRDlg->_ppEditView))->clearIndicator(SCE_UNIVERSAL_FOUND_STYLE_INC);
					(*(_pFRDlg->_ppEditView))->getFocus();
					display(false);
					return TRUE;

				case IDM_SEARCH_FINDINCREMENT:	// Accel table: Start incremental search
					// if focus is on a some other control, return it to the edit field
					if (::GetFocus() != ::GetDlgItem(_hSelf, IDC_INCFINDTEXT))
					{
						HWND hFindTxt = ::GetDlgItem(_hSelf, IDC_INCFINDTEXT);
						::PostMessage(_hSelf, WM_NEXTDLGCTL, reinterpret_cast<WPARAM>(hFindTxt), TRUE);
						return TRUE;
					}
					// otherwise, repeat the search
					[[fallthrough]];
				case IDM_SEARCH_FINDPREV:		// Accel table: find prev
				case IDM_SEARCH_FINDNEXT:		// Accel table: find next
				case IDC_INCFINDPREVOK:
				case IDC_INCFINDNXTOK:
				case IDOK:
					updateSearch = true;
					advance = true;
					forward = (LOWORD(wParam) == IDC_INCFINDNXTOK) ||
						(LOWORD(wParam) == IDM_SEARCH_FINDNEXT) ||
						(LOWORD(wParam) == IDM_SEARCH_FINDINCREMENT) ||
						((LOWORD(wParam) == IDOK) && !(GetKeyState(VK_SHIFT) & SHIFTED));
					break;

				case IDC_INCFINDMATCHCASE:
					updateSearch = true;
					updateCase = true;
					updateHiLight = true;
					break;

				case IDC_INCFINDHILITEALL:
					updateHiLight = true;
					break;

				case IDC_INCFINDTEXT:
					if (HIWORD(wParam) == EN_CHANGE)
					{
						updateSearch = true;
						updateHiLight = isCheckedOrNot(IDC_INCFINDHILITEALL);
						updateCase = isCheckedOrNot(IDC_INCFINDMATCHCASE);
						break;
					}
					// treat other edit notifications as unhandled
					[[fallthrough]];
				default:
					return FALSE;
			}
			FindOption fo;
			fo._isWholeWord = false;
			fo._incrementalType = advance ? NextIncremental : FirstIncremental;
			fo._whichDirection = forward ? DIR_DOWN : DIR_UP;
			fo._isMatchCase = (BST_CHECKED == ::SendDlgItemMessage(_hSelf, IDC_INCFINDMATCHCASE, BM_GETCHECK, 0, 0));

			generic_string str2Search = getTextFromCombo(::GetDlgItem(_hSelf, IDC_INCFINDTEXT));
			if (updateSearch)
			{
				FindStatus findStatus = FSFound;
				bool isFound = _pFRDlg->processFindNext(str2Search.c_str(), &fo, &findStatus);

				fo._str2Search = str2Search;
				int nbCounted = _pFRDlg->processAll(ProcessCountAll, &fo);
				setFindStatus(findStatus, nbCounted);

				// If case-sensitivity changed (to Match=yes), there may have been a matched selection that
				// now does not match; so if Not Found, clear selection and put caret at beginning of what was
				// selected (no change, if there was no selection)
				if (updateCase && !isFound)
				{
					Sci_CharacterRangeFull range = (*(_pFRDlg->_ppEditView))->getSelection();
					(*(_pFRDlg->_ppEditView))->execute(SCI_SETSEL, static_cast<WPARAM>(-1), range.cpMin);
				}
			}

			if (updateHiLight)
			{
				bool highlight = !str2Search.empty() &&
					(BST_CHECKED == ::SendDlgItemMessage(_hSelf, IDC_INCFINDHILITEALL, BM_GETCHECK, 0, 0));
				markSelectedTextInc(highlight, &fo);
			}
			return TRUE;
		}

		case WM_ERASEBKGND:
		{
			if (NppDarkMode::isEnabled())
			{
				RECT rcClient{};
				GetClientRect(_hSelf, &rcClient);
				::FillRect(reinterpret_cast<HDC>(wParam), &rcClient, NppDarkMode::getDarkerBackgroundBrush());
				return TRUE;
			}
			else
			{
				HWND hParent = ::GetParent(_hSelf);
				HDC winDC = (HDC)wParam;
				//RTL handling
				POINT pt = { 0, 0 }, ptOrig = { 0, 0 };
				::MapWindowPoints(_hSelf, hParent, &pt, 1);
				::OffsetWindowOrgEx((HDC)wParam, pt.x, pt.y, &ptOrig);
				LRESULT lResult = SendMessage(hParent, WM_ERASEBKGND, reinterpret_cast<WPARAM>(winDC), 0);
				::SetWindowOrgEx(winDC, ptOrig.x, ptOrig.y, NULL);
				return (BOOL)lResult;
			}
		}
	}
	return FALSE;
}

void FindIncrementDlg::markSelectedTextInc(bool enable, FindOption *opt)
{
	(*(_pFRDlg->_ppEditView))->clearIndicator(SCE_UNIVERSAL_FOUND_STYLE_INC);

	if (!enable)
		return;

	//Get selection
	Sci_CharacterRangeFull range = (*(_pFRDlg->_ppEditView))->getSelection();

	//If nothing selected, dont mark anything
	if (range.cpMin == range.cpMax)
		return;

	TCHAR text2Find[FINDREPLACE_MAXLENGTH]{};
	(*(_pFRDlg->_ppEditView))->getGenericSelectedText(text2Find, FINDREPLACE_MAXLENGTH, false);	//do not expand selection (false)
	opt->_str2Search = text2Find;
	_pFRDlg->markAllInc(opt);
}

void FindIncrementDlg::setFindStatus(FindStatus iStatus, int nbCounted)
{
	generic_string statusStr2Display;

	static const TCHAR* const strFSNotFound = TEXT("Phrase not found");
	static const TCHAR* const strFSTopReached = TEXT("Reached top of page, continued from bottom");
	static const TCHAR* const strFSEndReached = TEXT("Reached end of page, continued from top");

	NativeLangSpeaker* pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();

	if (nbCounted >= 0)
	{
		statusStr2Display = pNativeSpeaker->getLocalizedStrFromID("IncrementalFind-FSFound", TEXT(""));

		if (statusStr2Display.empty())
		{
			TCHAR strFindFSFound[128] = TEXT("");

			if (nbCounted == 1)
				wsprintf(strFindFSFound, TEXT("%d match"), nbCounted);
			else
				wsprintf(strFindFSFound, TEXT("%s matches"), commafyInt(nbCounted).c_str());
			statusStr2Display = strFindFSFound;
		}
		else
		{
			statusStr2Display = stringReplace(statusStr2Display, TEXT("$INT_REPLACE$"), std::to_wstring(nbCounted));
		}
	}


	switch (iStatus)
	{
		case FindStatus::FSNotFound:
			statusStr2Display = pNativeSpeaker->getLocalizedStrFromID("IncrementalFind-FSNotFound", strFSNotFound);
			break;

		case FindStatus::FSTopReached:
			statusStr2Display = pNativeSpeaker->getLocalizedStrFromID("IncrementalFind-FSTopReached", strFSTopReached);
			break;

		case FindStatus::FSEndReached:
			statusStr2Display = pNativeSpeaker->getLocalizedStrFromID("IncrementalFind-FSEndReached", strFSEndReached);
			break;

		case FindStatus::FSFound:
			break;

		default:
			return; // out of range
	}

	_findStatus = iStatus;

	// get the HWND of the editor
	HWND hEditor = ::GetDlgItem(_hSelf, IDC_INCFINDTEXT);

	// invalidate the editor rect
	::InvalidateRect(hEditor, NULL, TRUE);

	::SendDlgItemMessage(_hSelf, IDC_INCFINDSTATUS, WM_SETTEXT, 0, reinterpret_cast<LPARAM>(statusStr2Display.c_str()));
}

void FindIncrementDlg::addToRebar(ReBar * rebar)
{
	if (_pRebar)
		return;

	_pRebar = rebar;
	RECT client{};
	getClientRect(client);

	ZeroMemory(&_rbBand, REBARBAND_SIZE);
	_rbBand.cbSize  = REBARBAND_SIZE;

	_rbBand.fMask   = RBBIM_STYLE | RBBIM_CHILD | RBBIM_CHILDSIZE |
					  RBBIM_SIZE | RBBIM_ID;

	_rbBand.fStyle = RBBS_HIDDEN | RBBS_NOGRIPPER;
	_rbBand.hwndChild	= getHSelf();
	_rbBand.wID			= REBAR_BAR_SEARCH;	//ID REBAR_BAR_SEARCH for search dialog
	_rbBand.cxMinChild	= 0;
	_rbBand.cyIntegral	= 1;
	_rbBand.cyMinChild	= _rbBand.cyMaxChild	= client.bottom-client.top;
	_rbBand.cxIdeal		= _rbBand.cx			= client.right-client.left;

	_pRebar->addBand(&_rbBand, true);
	_pRebar->setGrayBackground(_rbBand.wID);
}

const TCHAR Progress::cClassName[] = TEXT("NppProgressClass");
const TCHAR Progress::cDefaultHeader[] = TEXT("Operation progress...");
const int Progress::cBackgroundColor = COLOR_3DFACE;
const int Progress::cPBwidth = NppParameters::getInstance()._dpiManager.scaleX(550);
const int Progress::cPBheight = NppParameters::getInstance()._dpiManager.scaleY(10);
const int Progress::cBTNwidth = NppParameters::getInstance()._dpiManager.scaleX(80);
const int Progress::cBTNheight = NppParameters::getInstance()._dpiManager.scaleY(25);


volatile LONG Progress::refCount = 0;


Progress::Progress(HINSTANCE hInst) : _hwnd(NULL), _hCallerWnd(NULL)
{
	if (::InterlockedIncrement(&refCount) == 1)
	{
		_hInst = hInst;

		WNDCLASSEX wcex{};
		wcex.cbSize = sizeof(wcex);
		wcex.style = CS_HREDRAW | CS_VREDRAW;
		wcex.lpfnWndProc = wndProc;
		wcex.hInstance = _hInst;
		wcex.hCursor = ::LoadCursor(NULL, IDC_ARROW);
		wcex.hbrBackground = ::GetSysColorBrush(cBackgroundColor);
		wcex.lpszClassName = cClassName;

		::RegisterClassEx(&wcex);

		INITCOMMONCONTROLSEX icex{};
		icex.dwSize = sizeof(icex);
		icex.dwICC = ICC_STANDARD_CLASSES | ICC_PROGRESS_CLASS;

		::InitCommonControlsEx(&icex);
	}
}


Progress::~Progress()
{
	close();

	if (::InterlockedDecrement(&refCount) == 0)
		::UnregisterClass(cClassName, _hInst);
}


HWND Progress::open(HWND hCallerWnd, const TCHAR* header)
{
	if (_hwnd)
		return _hwnd;

	// Create manually reset non-signalled event
	_hActiveState = ::CreateEvent(NULL, TRUE, FALSE, NULL);
	if (!_hActiveState)
		return NULL;

	if (!hCallerWnd)
		return NULL;

	_hCallerWnd = hCallerWnd;

	for (HWND hwnd = _hCallerWnd; hwnd; hwnd = ::GetParent(hwnd))
		::UpdateWindow(hwnd);

	if (header)
		wcscpy_s(_header, _countof(_header), header);
	else
		wcscpy_s(_header, _countof(_header), cDefaultHeader);

	_hThread = ::CreateThread(NULL, 0, threadFunc, this, 0, NULL);
	if (!_hThread)
	{
		::CloseHandle(_hActiveState);
		return NULL;
	}

	// Wait for the progress window to be created
	::WaitForSingleObject(_hActiveState, INFINITE);

	// On progress window create fail
	if (!_hwnd)
	{
		::WaitForSingleObject(_hThread, INFINITE);
		::CloseHandle(_hThread);
		::CloseHandle(_hActiveState);
	}

	return _hwnd;
}


void Progress::close()
{
	if (_hwnd)
	{
		::PostMessage(_hwnd, WM_CLOSE, 0, 0);
		_hwnd = NULL;
		::WaitForSingleObject(_hThread, INFINITE);

		::CloseHandle(_hThread);
		::CloseHandle(_hActiveState);
	}

	if (_hFont != nullptr)
	{
		::DeleteObject(_hFont);
		_hFont = nullptr;
	}
}


void Progress::setPercent(unsigned percent, const TCHAR* fileName, int nbHitsSoFar) const
{
	if (_hwnd)
	{
		::PostMessage(_hPBar, PBM_SETPOS, percent, 0);
		::SendMessage(_hPathText, WM_SETTEXT, 0, reinterpret_cast<LPARAM>(fileName));
		TCHAR str[16]{};
		_itow(nbHitsSoFar, str, 10);
		::SendMessage(_hRunningHitsText, WM_SETTEXT, 0, reinterpret_cast<LPARAM>(str));
	}
}


void Progress::setInfo(const TCHAR* info, int nbHitsSoFar) const
{
	if (_hwnd)
	{
		::SendMessage(_hPathText, WM_SETTEXT, 0, reinterpret_cast<LPARAM>(info));
		if (nbHitsSoFar != -1)
		{
			TCHAR str[16]{};
			_itow(nbHitsSoFar, str, 10);
			::SendMessage(_hRunningHitsText, WM_SETTEXT, 0, reinterpret_cast<LPARAM>(str));
		}
	}
}


DWORD WINAPI Progress::threadFunc(LPVOID data)
{
	Progress* pw = static_cast<Progress*>(data);
	return (DWORD)pw->thread();
}


int Progress::thread()
{
	BOOL r = createProgressWindow();
	::SetEvent(_hActiveState);
	if (r)
		return r;

	// Window message loop
	MSG msg{};
	while ((r = ::GetMessage(&msg, NULL, 0, 0)) != 0 && r != -1)
	{
		::TranslateMessage(&msg);
		::DispatchMessage(&msg);
	}

	return r;
}


int Progress::createProgressWindow()
{
	DPIManager& dpiManager = NppParameters::getInstance()._dpiManager;

	NativeLangSpeaker* pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();

	_hwnd = ::CreateWindowEx(
		WS_EX_APPWINDOW | WS_EX_TOOLWINDOW | WS_EX_OVERLAPPEDWINDOW |
			(pNativeSpeaker->isRTL() ? WS_EX_LAYOUTRTL : 0),
		cClassName, _header, WS_POPUP | WS_CAPTION,
		CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
		NULL, NULL, _hInst, (LPVOID)this);
	if (!_hwnd)
		return -1;

	const int paddedBorder = 3 * ::GetSystemMetrics(SM_CXPADDEDBORDER);
	int widthPadding = dpiManager.scaleX(15) + paddedBorder;
	int width = cPBwidth + widthPadding;

	int textHeight = dpiManager.scaleY(20);
	int progressBarPadding = dpiManager.scaleY(10);
	int morePadding = dpiManager.scaleY(45);
	int height = cPBheight + cBTNheight + textHeight + progressBarPadding + morePadding + paddedBorder;


	POINT center{};
	RECT callerRect{};
	::GetWindowRect(_hCallerWnd, &callerRect);
	center.x = (callerRect.left + callerRect.right) / 2;
	center.y = (callerRect.top + callerRect.bottom) / 2;

	int x = center.x - width / 2;
	int y = center.y - height / 2;
	::MoveWindow(_hwnd, x, y, width, height, TRUE);


	int xStartPos = dpiManager.scaleX(5);
	int yTextPos = dpiManager.scaleY(5);
	auto ctrlWidth = width - widthPadding - xStartPos;

	_hPathText = ::CreateWindowEx(0, TEXT("STATIC"), TEXT(""),
		WS_CHILD | WS_VISIBLE | SS_PATHELLIPSIS,
		xStartPos, yTextPos,
		ctrlWidth, textHeight, _hwnd, NULL, _hInst, NULL);

	generic_string hits = pNativeSpeaker->getLocalizedStrFromID("progress-hits-title", TEXT("Hits:"));
	_hRunningHitsStaticText = ::CreateWindowEx(0, TEXT("STATIC"), hits.c_str(),
		WS_CHILD | WS_VISIBLE | SS_RIGHT,
		xStartPos, yTextPos + textHeight * 2,
		75, textHeight, 
		_hwnd, NULL, _hInst, NULL);

	_hRunningHitsText = ::CreateWindowEx(0, TEXT("STATIC"), TEXT(""),
		WS_CHILD | WS_VISIBLE,
		xStartPos + 75 + 2, yTextPos + textHeight * 2,
		dpiManager.scaleX(70), textHeight,
		_hwnd, NULL, _hInst, NULL);

	_hPBar = ::CreateWindowEx(0, PROGRESS_CLASS, TEXT("Progress Bar"),
		WS_CHILD | WS_VISIBLE | PBS_SMOOTH,
		xStartPos, yTextPos + textHeight,
		ctrlWidth, cPBheight,
		_hwnd, NULL, _hInst, NULL);
	SendMessage(_hPBar, PBM_SETRANGE, 0, MAKELPARAM(0, 100));

	// Set border so user can distinguish easier progress bar,
	// especially, when getBackgroundColor is very similar or same 
	// as getDarkerBackgroundColor
	NppDarkMode::setBorder(_hPBar, NppDarkMode::isEnabled()); 
	NppDarkMode::disableVisualStyle(_hPBar, NppDarkMode::isEnabled());
	if (NppDarkMode::isEnabled())
	{
		::SendMessage(_hPBar, PBM_SETBKCOLOR, 0, static_cast<LPARAM>(NppDarkMode::getBackgroundColor()));
		::SendMessage(_hPBar, PBM_SETBARCOLOR, 0, static_cast<LPARAM>(NppDarkMode::getDarkerTextColor()));
	}

	generic_string cancel = pNativeSpeaker->getLocalizedStrFromID("progress-cancel-button", TEXT("Cancel"));

	_hBtn = ::CreateWindowEx(0, TEXT("BUTTON"), cancel.c_str(),
		WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON | BS_TEXT,
		(width - cBTNwidth) / 2, yTextPos + textHeight + cPBheight + progressBarPadding,
		cBTNwidth, cBTNheight,
		_hwnd, NULL, _hInst, NULL);

	if (_hFont == nullptr)
	{
		LOGFONT lf{ NppParameters::getDefaultGUIFont() };
		_hFont = ::CreateFontIndirect(&lf);
	}

	if (_hFont != nullptr)
	{
		const auto& wpFont = reinterpret_cast<WPARAM>(_hFont);
		::SendMessage(_hPathText, WM_SETFONT, wpFont, TRUE);
		::SendMessage(_hRunningHitsStaticText, WM_SETFONT, wpFont, TRUE);
		::SendMessage(_hRunningHitsText, WM_SETFONT, wpFont, TRUE);
		::SendMessage(_hBtn, WM_SETFONT, wpFont, TRUE);
	}

	NppDarkMode::autoSubclassAndThemeChildControls(_hwnd);
	NppDarkMode::setDarkTitleBar(_hwnd);

	::ShowWindow(_hwnd, SW_SHOWNORMAL);
	::UpdateWindow(_hwnd);

	return 0;
}


LRESULT APIENTRY Progress::wndProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam)
{
	switch (umsg)
	{
		case WM_CREATE:
		{
			Progress* pw = static_cast<Progress*>(reinterpret_cast<LPCREATESTRUCT>(lparam)->lpCreateParams);
			::SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pw));
			return 0;
		}

		case WM_CTLCOLORDLG:
		{
			if (NppDarkMode::isEnabled())
			{
				return NppDarkMode::onCtlColorDarker(reinterpret_cast<HDC>(wparam));
			}
			break;
		}

		case WM_CTLCOLORSTATIC:
		{
			if (NppDarkMode::isEnabled())
			{
				return NppDarkMode::onCtlColorDarker(reinterpret_cast<HDC>(wparam));
			}
			// transparent background for text, same as main window background
			return reinterpret_cast<LRESULT>(::GetSysColorBrush(NULL_BRUSH));
		}

		case WM_PRINTCLIENT:
		{
			if (NppDarkMode::isEnabled())
			{
				return TRUE;
			}
			break;
		}

		case WM_ERASEBKGND:
		{
			if (NppDarkMode::isEnabled())
			{
				RECT rc{};
				GetClientRect(hwnd, &rc);
				::FillRect(reinterpret_cast<HDC>(wparam), &rc, NppDarkMode::getDarkerBackgroundBrush());
			}
			return TRUE;
		}

		case WM_SETFOCUS:
		{
			Progress* pw =	reinterpret_cast<Progress*>(static_cast<LONG_PTR>
			(::GetWindowLongPtr(hwnd, GWLP_USERDATA)));
			::SetFocus(pw->_hBtn);
			return 0;
		}

		case WM_COMMAND:
			if (HIWORD(wparam) == BN_CLICKED)
			{
				Progress* pw = reinterpret_cast<Progress*>(static_cast<LONG_PTR>(::GetWindowLongPtr(hwnd, GWLP_USERDATA)));
				::ResetEvent(pw->_hActiveState);
				::EnableWindow(pw->_hBtn, FALSE);
				NativeLangSpeaker *pNativeSpeaker = (NppParameters::getInstance()).getNativeLangSpeaker();
				generic_string info = pNativeSpeaker->getLocalizedStrFromID("progress-cancel-info", TEXT("Cancelling operation, please wait..."));
				pw->setInfo(info.c_str());
				return 0;
			}
			break;

		case WM_DESTROY:
			::PostQuitMessage(0);
			return 0;
	}

	return ::DefWindowProc(hwnd, umsg, wparam, lparam);
}
