1
0
Fork 0
mirror of https://github.com/beefytech/Beef.git synced 2025-06-08 11:38:21 +02:00
Beef/IDE/src/ui/FindResultsPanel.bf
2020-03-23 12:07:05 -07:00

738 lines
20 KiB
Beef

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Beefy.widgets;
using Beefy.utils;
using Beefy.theme.dark;
using System.Threading;
using Beefy;
using System.IO;
using System.Diagnostics;
using IDE.Util;
namespace IDE.ui
{
public class FindResultsPanel : OutputPanel
{
public static String sCurrentDocument = "Current Document";
public static String sCurrentProject = "Current Project";
public static String sEntireSolution = "Entire Solution";
public static String[] sLocationStrings = new .(sCurrentDocument, sCurrentProject, sEntireSolution) ~ delete _;
List<String> mPendingLines = new List<String>() ~ DeleteContainerAndItems!(_);
int32 mCurLineNum;
HashSet<String> mFoundPathSet ~ DeleteContainerAndItems!(_);
List<String> mSearchPaths ~ DeleteContainerAndItems!(_);
SearchOptions mSearchOptions ~ delete _;
Thread mSearchThread ~ delete _;
Monitor mMonitor = new Monitor() ~ delete _;
bool mCancelling;
//bool mAbortSearch;
public class SearchOptions
{
public String mSearchString ~ delete _;
public String mReplaceString ~ delete _;
public String mSearchLocation ~ delete _;
public List<String> mFileTypes ~ DeleteContainerAndItems!(_);
public bool mMatchCase;
public bool mMatchWholeWord;
public bool mRecurseDirectories;
public bool mDeferredFindFiles;
}
// This is so we can remap to the correct source location even if we add or remove lines above the found line
class LineSrcInfo
{
public FileEditData mEditData;
public int32 mCharId;
public int32 mLine;
public int32 mLineChar;
}
List<LineSrcInfo> mLineSrcInfo = new List<LineSrcInfo>() ~ DeleteContainerAndItems!(_);
bool mHasUnboundLineSrcInfos;
int32 mLastEditDataRevision;
class FindInfo
{
public String mContent ~ delete _;
}
Dictionary<String, FindInfo> mFindInfoMap = new Dictionary<String, FindInfo>() ~ { for (var kv in mFindInfoMap) delete kv.value; delete _; };
List<String> mDeferredReplacePaths = new List<String>() ~ DeleteContainerAndItems!(_);
public this()
{
((OutputWidgetContent)mOutputWidget.mEditWidgetContent).mGotoReferenceEvent.Add(new => GotoRefrenceAtLine);
}
bool GotoRefrenceAtLine(int line, int lineOfs)
{
if (line >= mLineSrcInfo.Count)
return false;
var lineSrcInfo = ref mLineSrcInfo[line];
if (lineSrcInfo == null)
return false;
if (lineSrcInfo.mEditData.mEditWidget == null)
return false;
var editWidgetContent = lineSrcInfo.mEditData.mEditWidget.mEditWidgetContent;
if (editWidgetContent == null)
return false;
int charIdx = editWidgetContent.mData.mTextIdData.GetPrepared().GetIndexFromId(lineSrcInfo.mCharId);
if (charIdx == -1)
return false;
int remappedLine;
int remappedLineChar;
editWidgetContent.GetLineCharAtIdx(charIdx, out remappedLine, out remappedLineChar);
var filePath = lineSrcInfo.mEditData.mFilePath;
IDEApp.sApp.ShowSourceFileLocation(filePath, -1, -1/*IDEApp.sApp.mWorkspace.GetHighestCompileIdx()*/, remappedLine, remappedLineChar, LocatorType.Always);
return true;
}
bool IsWordChar(char32 c32)
{
return c32.IsLetterOrDigit || (c32 == '_');
}
void SearchThread()
{
int32 linesMatched = 0;
int32 filesMatched = 0;
int32 filesSearched = 0;
if (mSearchOptions.mDeferredFindFiles)
{
AddFilesFromDirectory(mSearchOptions.mSearchLocation, mSearchOptions);
}
String line = scope String();
for (var filePath in mSearchPaths)
FileBlock:
{
filesSearched++;
if (mCancelling)
break;
StreamReader reader = null;
String fileText = null;
if (mSearchOptions.mSearchString.IsEmpty)
break;
FindInfo findInfo;
if (mFindInfoMap.TryGetValue(filePath, out findInfo))
{
fileText = findInfo.mContent;
}
if (fileText != null)
{
let strStream = scope:FileBlock StringStream(fileText, .Reference);
reader = scope:FileBlock StreamReader(strStream, UTF8Encoding.UTF8, false, 1024);
}
else
{
let streamReader = new StreamReader();
if (streamReader.Open(filePath) case .Err(let errCode))
{
delete streamReader;
if (errCode != .NotFound)
QueueLine(scope String()..AppendF("Failed to open file: {0}", filePath));
continue;
}
reader = streamReader;
defer:FileBlock delete reader;
}
bool hasDeferredReplace = false;
if (reader != null)
{
char8* searchPtr = mSearchOptions.mSearchString.Ptr;
int searchLength = mSearchOptions.mSearchString.Length;
bool hadMatch = false;
int32 lineNum = 0;
while (!reader.EndOfStream)
{
if (mCancelling)
break;
line.Clear();
reader.ReadLine(line);
char8* linePtr = line.Ptr;
bool lineMatched;
if (mSearchOptions.mMatchWholeWord)
{
bool isNewStart = true;
int lineIdx = 0;
for (let c32 in line.DecodedChars)
{
if ((isNewStart) && (mSearchOptions.mMatchCase ?
String.[Friend]EqualsHelper(linePtr + lineIdx, searchPtr, searchLength) :
String.[Friend]EqualsIgnoreCaseHelper(linePtr + lineIdx, searchPtr, searchLength)))
{
int checkIdx = lineIdx + searchLength;
if (checkIdx >= line.Length)
{
lineMatched = true;
break;
}
char32 nextC = line.GetChar32(checkIdx).c;
if (!IsWordChar(nextC))
{
lineMatched = true;
break;
}
}
isNewStart = !IsWordChar(c32);
lineIdx = @c32.NextIndex;
}
}
else
lineMatched = line.IndexOf(mSearchOptions.mSearchString, !mSearchOptions.mMatchCase) != -1;
if (lineMatched)
{
linesMatched++;
hadMatch = true;
if (mSearchOptions.mReplaceString != null)
{
hasDeferredReplace = true;
break;
}
else
{
const int maxLen = 4096;
if (line.Length >= maxLen)
{
line.RemoveToEnd(maxLen);
line.Append("...");
}
for (var c in ref line.RawChars)
{
if (c.IsControl)
c = ' ';
}
QueueLine(filePath, lineNum, 0, line);
}
}
lineNum++;
}
if (hadMatch)
filesMatched++;
}
if (hasDeferredReplace)
{
mDeferredReplacePaths.Add(filePath);
}
}
String outLine = scope String();
if (mSearchOptions.mReplaceString != null)
outLine.AppendF("Replacing text in {0} file{1}. Total files searched: {2}", filesMatched, (filesMatched == 1) ? "" : "s", filesSearched);
else
outLine.AppendF("Matching lines: {0} Matching files: {1} Total files searched: {2}", linesMatched, filesMatched, filesSearched);
QueueLine(outLine);
if (mCancelling)
QueueLine("Search cancelled");
}
public bool IsSearching
{
get
{
return mSearchThread != null;
}
}
public void CancelSearch()
{
mCancelling = true;
}
void StopSearch()
{
if (mSearchThread != null)
{
mSearchThread.Join();
DeleteAndNullify!(mSearchThread);
DeleteAndNullify!(mSearchOptions);
ClearAndDeleteItems(mPendingLines);
DeleteContainerAndItems!(mSearchPaths);
mSearchPaths = null;
DeleteContainerAndItems!(mFoundPathSet);
mFoundPathSet = null;
for (var kv in mFindInfoMap)
delete kv.value;
mFindInfoMap.Clear();
mDeferredReplacePaths.Clear();
mCancelling = false;
}
}
bool PassesFilter(StringView fileName, SearchOptions searchOptions)
{
bool passes = false;
if (searchOptions.mFileTypes.IsEmpty)
passes = true;
for (let fileType in searchOptions.mFileTypes)
{
if (fileType == "*")
{
passes = true;
continue;
}
if (Path.WildcareCompare(fileName, fileType))
{
passes = true;
break;
}
}
return passes;
}
void AddFromFilesFolder(ProjectFolder projectFolder, SearchOptions searchOptions)
{
for (var projectEntry in projectFolder.mChildItems)
{
var childFolder = projectEntry as ProjectFolder;
if (childFolder != null)
AddFromFilesFolder(childFolder, searchOptions);
var projectSource = projectEntry as ProjectSource;
if (projectSource != null)
{
var path = scope String();
projectSource.GetFullImportPath(path);
var upperPath = scope String(path);
IDEUtils.FixFilePath(upperPath);
if (!Environment.IsFileSystemCaseSensitive)
upperPath.ToUpper();
if ((!mFoundPathSet.Contains(upperPath)) && (PassesFilter(path, searchOptions)))
{
mSearchPaths.Add(new String(path));
mFoundPathSet.Add(new String(upperPath));
}
}
}
}
void AddFilesFromDirectory(StringView dirPath, SearchOptions searchOptions)
{
if (mCancelling)
return;
for (var fileEntry in Directory.EnumerateFiles(dirPath))
{
if (mCancelling)
return;
var fileName = scope String();
fileEntry.GetFileName(fileName);
if (PassesFilter(fileName, searchOptions))
{
var filePath = new String();
fileEntry.GetFilePath(filePath);
var upperPath = new String(filePath);
IDEUtils.FixFilePath(upperPath);
if (!Environment.IsFileSystemCaseSensitive)
upperPath.ToUpper();
if (mFoundPathSet.Add(upperPath))
{
mSearchPaths.Add(filePath);
}
else
{
delete upperPath;
delete filePath;
}
}
}
if (searchOptions.mRecurseDirectories)
{
for (var dirEntry in Directory.EnumerateDirectories(dirPath))
{
if (mCancelling)
return;
var newDirPath = scope String();
dirEntry.GetFilePath(newDirPath);
AddFilesFromDirectory(newDirPath, searchOptions);
}
}
}
public void Search(SearchOptions searchOptions)
{
if (mSearchThread != null)
StopSearch();
mCancelling = false;
Clear();
QueueLine("Searching...");
mSearchPaths = new List<String>();
mFoundPathSet = new HashSet<String>();
if (searchOptions.mSearchLocation == sEntireSolution)
{
for (var project in IDEApp.sApp.mWorkspace.mProjects)
{
AddFromFilesFolder(project.mRootFolder, searchOptions);
}
}
else if (searchOptions.mSearchLocation == sCurrentDocument)
{
var sourceViewPanel = gApp.GetActiveSourceViewPanel(true);
if (sourceViewPanel != null)
{
mSearchPaths.Add(new String(sourceViewPanel.mFilePath));
}
}
else if (searchOptions.mSearchLocation == sCurrentProject)
{
var sourceViewPanel = gApp.GetActiveSourceViewPanel(true);
if (sourceViewPanel != null)
{
if (sourceViewPanel.mProjectSource != null)
AddFromFilesFolder(sourceViewPanel.mProjectSource.mProject.mRootFolder, searchOptions);
}
}
else
{
searchOptions.mDeferredFindFiles = true;
}
for (var filePath in mSearchPaths)
{
String fileText = null;
var editData = gApp.GetEditData(filePath, false, false);
if (editData != null)
{
if (editData.mEditWidget != null)
{
fileText = new String();
editData.mEditWidget.GetText(fileText);
}
else if (editData.mSavedContent != null)
{
fileText = new String();
fileText.Append(editData.mSavedContent);
}
}
if (fileText != null)
{
FindInfo findInfo = new FindInfo();
findInfo.mContent = fileText;
mFindInfoMap[filePath] = findInfo;
}
}
if (searchOptions.mDeferredFindFiles)
{
String dirPath = scope String(searchOptions.mSearchLocation);
IDEUtils.FixFilePath(dirPath);
if (!dirPath.EndsWith(Path.DirectorySeparatorChar))
dirPath.Append(Path.DirectorySeparatorChar);
using (gApp.mMonitor.Enter())
{
for (let kv in gApp.mFileEditData)
{
if (kv.key.StartsWith(dirPath, Environment.IsFileSystemCaseSensitive ? .Ordinal : .OrdinalIgnoreCase))
{
let editData = kv.value;
if (!PassesFilter(editData.mFilePath, searchOptions))
continue;
String fileText = null;
if (editData.mEditWidget != null)
{
fileText = new String();
editData.mEditWidget.GetText(fileText);
}
else if (editData.mSavedContent != null)
{
fileText = new String();
fileText.Append(editData.mSavedContent);
}
if (fileText != null)
{
let filePath = new String(editData.mFilePath);
FindInfo findInfo = new FindInfo();
findInfo.mContent = fileText;
mFindInfoMap[filePath] = findInfo;
mSearchPaths.Add(filePath);
mFoundPathSet.Add(new String(kv.key));
}
}
}
}
}
mSearchOptions = searchOptions;
mSearchThread = new Thread(new => SearchThread);
mSearchThread.Start(false);
}
public override void Serialize(StructuredData data)
{
data.Add("Type", "FindResultsPanel");
}
public override bool Deserialize(StructuredData data)
{
return base.Deserialize(data);
}
public void QueueLine(String text)
{
mCurLineNum++;
using (mMonitor.Enter())
mPendingLines.Add(new String(text));
}
void RecordLineData(int32 ouputLineNum, FileEditData fileEditData)
{
}
public void QueueLine(FileEditData fileEditData, int32 line, int32 lineChar, String lineStr)
{
RecordLineData(mCurLineNum, fileEditData);
int charId = -1;
if (fileEditData.mEditWidget != null)
{
let editWidgetContent = fileEditData.mEditWidget.mEditWidgetContent;
int textIdx = editWidgetContent.GetTextIdx(line, lineChar);
charId = editWidgetContent.mData.mTextIdData.GetIdAtIndex(textIdx);
}
else
{
mHasUnboundLineSrcInfos = true;
}
while (mCurLineNum >= mLineSrcInfo.Count)
mLineSrcInfo.Add(null);
var lineSrcInfo = new LineSrcInfo();
lineSrcInfo.mCharId = (int32)charId;
lineSrcInfo.mEditData = fileEditData;
lineSrcInfo.mLine = line;
lineSrcInfo.mLineChar = lineChar;
mLineSrcInfo[mCurLineNum] = lineSrcInfo;
String outStr = scope String();
outStr.AppendF("{0}({1}):{2}", fileEditData.mFilePath, line + 1, lineStr);
gApp.mFindResultsPanel.QueueLine(outStr);
}
public void QueueLine(String fileName, int32 line, int32 column, String text)
{
QueueLine(gApp.GetEditData(fileName, true, false), line, column, text);
}
public override void Update()
{
base.Update();
// Try to bind locations to charIds if new files have been loaded
if ((mHasUnboundLineSrcInfos) && (mLastEditDataRevision != IDEApp.sApp.mFileDataDataRevision))
{
mHasUnboundLineSrcInfos = false;
for (var lineSrcInfo in mLineSrcInfo)
{
if (lineSrcInfo == null)
continue;
if (lineSrcInfo.mCharId != -1)
continue;
if (lineSrcInfo.mEditData.mEditWidget == null)
{
mHasUnboundLineSrcInfos = true;
continue;
}
let editWidgetContent = lineSrcInfo.mEditData.mEditWidget.mEditWidgetContent;
int textIdx = editWidgetContent.GetTextIdx(lineSrcInfo.mLine, lineSrcInfo.mLineChar);
lineSrcInfo.mCharId = (int32)editWidgetContent.mData.mTextIdData.GetIdAtIndex((int32)textIdx);
}
mLastEditDataRevision = IDEApp.sApp.mFileDataDataRevision;
}
using (mMonitor.Enter())
{
if (mPendingLines.Count > 0)
{
String sb = scope String();
while (mPendingLines.Count > 0)
{
var pendingLine = mPendingLines.PopFront();
sb.Append(pendingLine, "\n");
delete pendingLine;
}
Write(sb);
}
}
if ((mSearchThread != null) && (mSearchThread.Join(0)))
{
if (!mDeferredReplacePaths.IsEmpty)
{
GlobalUndoData globalUndoData = gApp.mGlobalUndoManager.CreateUndoData();
int replaceCount = 0;
for (var filePath in mDeferredReplacePaths)
{
int replaceInFileCount = 0;
var editData = gApp.GetEditData(filePath);
bool hasUnsavedChanges = editData.HasTextChanged();
globalUndoData.mFileEditDatas.Add(editData);
var sourceEditBatchHelper = scope:: SourceEditBatchHelper(editData.mEditWidget, "#renameInFiles", new GlobalUndoAction(editData, globalUndoData));
bool matchCase = mSearchOptions.mMatchCase;
let editWidgetContent = editData.mEditWidget.mEditWidgetContent;
let data = editData.mEditWidget.mEditWidgetContent.mData;
//let searchStrPtr = mSearchOptions.mSearchString.Ptr;
bool isNewStart = true;
//for (int i = 0; i < data.mTextLength - mSearchOptions.mSearchString.Length; i++)
int i = 0;
while (i < data.mTextLength - mSearchOptions.mSearchString.Length)
{
if ((isNewStart) || (!mSearchOptions.mMatchWholeWord))
{
bool matches = true;
int searchOfs = 0;
while (searchOfs < mSearchOptions.mSearchString.Length)
{
var (checkC, charBytes) = editWidgetContent.GetChar32(i + searchOfs);
char32 searchC = mSearchOptions.mSearchString.GetChar32(searchOfs).c;
if (!matchCase)
{
checkC = checkC.ToUpper;
searchC = searchC.ToUpper;
}
if (checkC != searchC)
{
matches = false;
break;
}
searchOfs += charBytes;
}
if ((matches) && (mSearchOptions.mMatchWholeWord))
{
int nextIdx = i + mSearchOptions.mSearchString.Length;
if (nextIdx < data.mTextLength)
{
var nextC = editWidgetContent.GetChar32(nextIdx).0;
if (IsWordChar(nextC))
matches = false;
}
}
if (matches)
{
editWidgetContent.CursorTextPos = i;
editWidgetContent.mSelection = EditSelection(i, i + mSearchOptions.mSearchString.Length);
var insertTextAction = new EditWidgetContent.InsertTextAction(editWidgetContent, mSearchOptions.mReplaceString, .None);
insertTextAction.mMoveCursor = false;
editWidgetContent.mData.mUndoManager.Add(insertTextAction);
editWidgetContent.PhysDeleteSelection(false);
editWidgetContent.PhysInsertAtCursor(mSearchOptions.mReplaceString, false);
i += mSearchOptions.mSearchString.Length - 1;
replaceCount++;
replaceInFileCount++;
}
}
var (c32, charBytes) = editWidgetContent.GetChar32(i);
if (mSearchOptions.mMatchWholeWord)
isNewStart = !IsWordChar(c32);
i += charBytes;
}
sourceEditBatchHelper.Finish();
if (!hasUnsavedChanges)
{
gApp.SaveFile(editData);
}
if ((IDEApp.IsBeefFile(filePath)) && (gApp.mBfResolveCompiler != null))
{
for (var projectSource in editData.mProjectSources)
gApp.mBfResolveCompiler.QueueProjectSource(projectSource, false);
gApp.mBfResolveCompiler.QueueDeferredResolveAll();
}
var line = scope String();
line.AppendF(" {0}: Replaced {1} instance{2}\n", filePath, replaceInFileCount, (replaceInFileCount == 1) ? "" : "s");
Write(line);
}
}
StopSearch();
}
}
public override void Clear()
{
base.Clear();
ClearAndDeleteItems(mLineSrcInfo);
mHasUnboundLineSrcInfos = false;
mCurLineNum = 0;
}
public override bool HasAffinity(Widget otherPanel)
{
return base.HasAffinity(otherPanel) || (otherPanel is OutputPanel);
}
}
}