TextEditor.cpp 86 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969
  1. #include <algorithm>
  2. #include <string>
  3. #include <cmath>
  4. #include <set>
  5. #include "TextEditor.h"
  6. #define IMGUI_SCROLLBAR_WIDTH 14.0f
  7. #define POS_TO_COORDS_COLUMN_OFFSET 0.33f
  8. #define IMGUI_DEFINE_MATH_OPERATORS
  9. #include "imgui.h" // for imGui::GetCurrentWindow()
  10. // --------------------------------------- //
  11. // ------------- Exposed API ------------- //
  12. TextEditor::TextEditor()
  13. {
  14. SetPalette(defaultPalette);
  15. mLines.push_back(Line());
  16. }
  17. TextEditor::~TextEditor()
  18. {
  19. }
  20. void TextEditor::SetPalette(PaletteId aValue)
  21. {
  22. mPaletteId = aValue;
  23. const Palette* palletteBase;
  24. switch (mPaletteId)
  25. {
  26. case PaletteId::Dark:
  27. default:
  28. palletteBase = &(GetDarkPalette());
  29. break;
  30. case PaletteId::Light:
  31. palletteBase = &(GetLightPalette());
  32. break;
  33. case PaletteId::Mariana:
  34. palletteBase = &(GetMarianaPalette());
  35. break;
  36. case PaletteId::RetroBlue:
  37. palletteBase = &(GetRetroBluePalette());
  38. break;
  39. }
  40. /* Update palette with the current alpha from style */
  41. for (int i = 0; i < (int)PaletteIndex::Max; ++i)
  42. {
  43. ImVec4 color = U32ColorToVec4((*palletteBase)[i]);
  44. color.w *= ImGui::GetStyle().Alpha;
  45. mPalette[i] = ImGui::ColorConvertFloat4ToU32(color);
  46. }
  47. }
  48. void TextEditor::SetLanguageDefinition(LanguageDefinitionId aValue)
  49. {
  50. mLanguageDefinitionId = aValue;
  51. switch (mLanguageDefinitionId)
  52. {
  53. case LanguageDefinitionId::None:
  54. mLanguageDefinition = nullptr;
  55. return;
  56. case LanguageDefinitionId::Cpp:
  57. mLanguageDefinition = &(LanguageDefinition::Cpp());
  58. break;
  59. case LanguageDefinitionId::C:
  60. mLanguageDefinition = &(LanguageDefinition::C());
  61. break;
  62. case LanguageDefinitionId::Cs:
  63. mLanguageDefinition = &(LanguageDefinition::Cs());
  64. break;
  65. case LanguageDefinitionId::Python:
  66. mLanguageDefinition = &(LanguageDefinition::Python());
  67. break;
  68. case LanguageDefinitionId::Lua:
  69. mLanguageDefinition = &(LanguageDefinition::Lua());
  70. break;
  71. case LanguageDefinitionId::Json:
  72. mLanguageDefinition = &(LanguageDefinition::Json());
  73. break;
  74. case LanguageDefinitionId::Sql:
  75. mLanguageDefinition = &(LanguageDefinition::Sql());
  76. break;
  77. case LanguageDefinitionId::AngelScript:
  78. mLanguageDefinition = &(LanguageDefinition::AngelScript());
  79. break;
  80. case LanguageDefinitionId::Glsl:
  81. mLanguageDefinition = &(LanguageDefinition::Glsl());
  82. break;
  83. case LanguageDefinitionId::Hlsl:
  84. mLanguageDefinition = &(LanguageDefinition::Hlsl());
  85. break;
  86. }
  87. mRegexList.clear();
  88. for (const auto& r : mLanguageDefinition->mTokenRegexStrings)
  89. mRegexList.push_back(std::make_pair(boost::regex(r.first, boost::regex_constants::optimize), r.second));
  90. Colorize();
  91. }
  92. const char* TextEditor::GetLanguageDefinitionName() const
  93. {
  94. return mLanguageDefinition != nullptr ? mLanguageDefinition->mName.c_str() : "None";
  95. }
  96. void TextEditor::SetTabSize(int aValue)
  97. {
  98. mTabSize = Max(1, Min(8, aValue));
  99. }
  100. void TextEditor::SetLineSpacing(float aValue)
  101. {
  102. mLineSpacing = Max(1.0f, Min(2.0f, aValue));
  103. }
  104. void TextEditor::SelectAll()
  105. {
  106. ClearSelections();
  107. ClearExtraCursors();
  108. MoveTop();
  109. MoveBottom(true);
  110. }
  111. void TextEditor::SelectLine(int aLine)
  112. {
  113. ClearSelections();
  114. ClearExtraCursors();
  115. SetSelection({ aLine, 0 }, { aLine, GetLineMaxColumn(aLine) });
  116. }
  117. void TextEditor::SelectRegion(int aStartLine, int aStartChar, int aEndLine, int aEndChar)
  118. {
  119. ClearSelections();
  120. ClearExtraCursors();
  121. SetSelection(aStartLine, aStartChar, aEndLine, aEndChar);
  122. }
  123. void TextEditor::SelectNextOccurrenceOf(const char* aText, int aTextSize, bool aCaseSensitive)
  124. {
  125. ClearSelections();
  126. ClearExtraCursors();
  127. SelectNextOccurrenceOf(aText, aTextSize, -1, aCaseSensitive);
  128. }
  129. void TextEditor::SelectAllOccurrencesOf(const char* aText, int aTextSize, bool aCaseSensitive)
  130. {
  131. ClearSelections();
  132. ClearExtraCursors();
  133. SelectNextOccurrenceOf(aText, aTextSize, -1, aCaseSensitive);
  134. Coordinates startPos = mState.mCursors[mState.GetLastAddedCursorIndex()].mInteractiveEnd;
  135. while (true)
  136. {
  137. AddCursorForNextOccurrence(aCaseSensitive);
  138. Coordinates lastAddedPos = mState.mCursors[mState.GetLastAddedCursorIndex()].mInteractiveEnd;
  139. if (lastAddedPos == startPos)
  140. break;
  141. }
  142. }
  143. bool TextEditor::AnyCursorHasSelection() const
  144. {
  145. for (int c = 0; c <= mState.mCurrentCursor; c++)
  146. if (mState.mCursors[c].HasSelection())
  147. return true;
  148. return false;
  149. }
  150. bool TextEditor::AllCursorsHaveSelection() const
  151. {
  152. for (int c = 0; c <= mState.mCurrentCursor; c++)
  153. if (!mState.mCursors[c].HasSelection())
  154. return false;
  155. return true;
  156. }
  157. void TextEditor::ClearExtraCursors()
  158. {
  159. mState.mCurrentCursor = 0;
  160. }
  161. void TextEditor::ClearSelections()
  162. {
  163. for (int c = mState.mCurrentCursor; c > -1; c--)
  164. mState.mCursors[c].mInteractiveEnd =
  165. mState.mCursors[c].mInteractiveStart =
  166. mState.mCursors[c].GetSelectionEnd();
  167. }
  168. void TextEditor::SetCursorPosition(int aLine, int aCharIndex)
  169. {
  170. SetCursorPosition({ aLine, GetCharacterColumn(aLine, aCharIndex) }, -1, true);
  171. }
  172. int TextEditor::GetFirstVisibleLine()
  173. {
  174. return mFirstVisibleLine;
  175. }
  176. int TextEditor::GetLastVisibleLine()
  177. {
  178. return mLastVisibleLine;
  179. }
  180. void TextEditor::SetViewAtLine(int aLine, SetViewAtLineMode aMode)
  181. {
  182. mSetViewAtLine = aLine;
  183. mSetViewAtLineMode = aMode;
  184. }
  185. void TextEditor::Copy()
  186. {
  187. if (AnyCursorHasSelection())
  188. {
  189. std::string clipboardText = GetClipboardText();
  190. ImGui::SetClipboardText(clipboardText.c_str());
  191. }
  192. else
  193. {
  194. if (!mLines.empty())
  195. {
  196. std::string str;
  197. auto& line = mLines[GetActualCursorCoordinates().mLine];
  198. for (auto& g : line)
  199. str.push_back(g.mChar);
  200. ImGui::SetClipboardText(str.c_str());
  201. }
  202. }
  203. }
  204. void TextEditor::Cut()
  205. {
  206. if (mReadOnly)
  207. {
  208. Copy();
  209. }
  210. else
  211. {
  212. if (AnyCursorHasSelection())
  213. {
  214. UndoRecord u;
  215. u.mBefore = mState;
  216. Copy();
  217. for (int c = mState.mCurrentCursor; c > -1; c--)
  218. {
  219. u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].GetSelectionStart(), mState.mCursors[c].GetSelectionEnd(), UndoOperationType::Delete });
  220. DeleteSelection(c);
  221. }
  222. u.mAfter = mState;
  223. AddUndo(u);
  224. }
  225. }
  226. }
  227. void TextEditor::Paste()
  228. {
  229. if (mReadOnly)
  230. return;
  231. // check if we should do multicursor paste
  232. std::string clipText = ImGui::GetClipboardText();
  233. bool canPasteToMultipleCursors = false;
  234. std::vector<std::pair<int, int>> clipTextLines;
  235. if (mState.mCurrentCursor > 0)
  236. {
  237. clipTextLines.push_back({ 0,0 });
  238. for (int i = 0; i < clipText.length(); i++)
  239. {
  240. if (clipText[i] == '\n')
  241. {
  242. clipTextLines.back().second = i;
  243. clipTextLines.push_back({ i + 1, 0 });
  244. }
  245. }
  246. clipTextLines.back().second = clipText.length();
  247. canPasteToMultipleCursors = clipTextLines.size() == mState.mCurrentCursor + 1;
  248. }
  249. if (clipText.length() > 0)
  250. {
  251. UndoRecord u;
  252. u.mBefore = mState;
  253. if (AnyCursorHasSelection())
  254. {
  255. for (int c = mState.mCurrentCursor; c > -1; c--)
  256. {
  257. u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].GetSelectionStart(), mState.mCursors[c].GetSelectionEnd(), UndoOperationType::Delete });
  258. DeleteSelection(c);
  259. }
  260. }
  261. for (int c = mState.mCurrentCursor; c > -1; c--)
  262. {
  263. Coordinates start = GetActualCursorCoordinates(c);
  264. if (canPasteToMultipleCursors)
  265. {
  266. std::string clipSubText = clipText.substr(clipTextLines[c].first, clipTextLines[c].second - clipTextLines[c].first);
  267. InsertTextAtCursor(clipSubText, c);
  268. u.mOperations.push_back({ clipSubText, start, GetActualCursorCoordinates(c), UndoOperationType::Add });
  269. }
  270. else
  271. {
  272. InsertTextAtCursor(clipText, c);
  273. u.mOperations.push_back({ clipText, start, GetActualCursorCoordinates(c), UndoOperationType::Add });
  274. }
  275. }
  276. u.mAfter = mState;
  277. AddUndo(u);
  278. }
  279. }
  280. void TextEditor::Undo(int aSteps)
  281. {
  282. while (CanUndo() && aSteps-- > 0)
  283. mUndoBuffer[--mUndoIndex].Undo(this);
  284. }
  285. void TextEditor::Redo(int aSteps)
  286. {
  287. while (CanRedo() && aSteps-- > 0)
  288. mUndoBuffer[mUndoIndex++].Redo(this);
  289. }
  290. void TextEditor::SetText(const std::string& aText)
  291. {
  292. mLines.clear();
  293. mLines.emplace_back(Line());
  294. for (auto chr : aText)
  295. {
  296. if (chr == '\r')
  297. continue;
  298. if (chr == '\n')
  299. mLines.emplace_back(Line());
  300. else
  301. {
  302. mLines.back().emplace_back(Glyph(chr, PaletteIndex::Default));
  303. }
  304. }
  305. mScrollToTop = true;
  306. mUndoBuffer.clear();
  307. mUndoIndex = 0;
  308. Colorize();
  309. }
  310. std::string TextEditor::GetText(const Coordinates& aStart, const Coordinates& aEnd) const
  311. {
  312. assert(aEnd > aStart);
  313. std::string result;
  314. auto lstart = aStart.mLine;
  315. auto lend = aEnd.mLine;
  316. auto istart = GetCharacterIndexR(aStart);
  317. auto iend = GetCharacterIndexR(aEnd);
  318. size_t s = 0;
  319. for (size_t i = lstart; i < lend; i++)
  320. s += mLines[i].size();
  321. result.reserve(s + s / 8);
  322. while (istart < iend || lstart < lend)
  323. {
  324. if (lstart >= (int)mLines.size())
  325. break;
  326. auto& line = mLines[lstart];
  327. if (istart < (int)line.size())
  328. {
  329. result += line[istart].mChar;
  330. istart++;
  331. }
  332. else
  333. {
  334. istart = 0;
  335. ++lstart;
  336. result += '\n';
  337. }
  338. }
  339. // Erase the NULL characters that are left because the text editor works with chars internally
  340. result.erase(std::find(result.begin(), result.end(), '\0'), result.end());
  341. return result;
  342. }
  343. void TextEditor::SetTextLines(const std::vector<std::string>& aLines)
  344. {
  345. mLines.clear();
  346. if (aLines.empty())
  347. mLines.emplace_back(Line());
  348. else
  349. {
  350. mLines.resize(aLines.size());
  351. for (size_t i = 0; i < aLines.size(); ++i)
  352. {
  353. const std::string& aLine = aLines[i];
  354. mLines[i].reserve(aLine.size());
  355. for (size_t j = 0; j < aLine.size(); ++j)
  356. mLines[i].emplace_back(Glyph(aLine[j], PaletteIndex::Default));
  357. }
  358. }
  359. mScrollToTop = true;
  360. mUndoBuffer.clear();
  361. mUndoIndex = 0;
  362. Colorize();
  363. }
  364. std::vector<std::string> TextEditor::GetTextLines() const
  365. {
  366. std::vector<std::string> result;
  367. result.reserve(mLines.size());
  368. for (auto& line : mLines)
  369. {
  370. std::string text;
  371. text.resize(line.size());
  372. for (size_t i = 0; i < line.size(); ++i)
  373. text[i] = line[i].mChar;
  374. result.emplace_back(std::move(text));
  375. }
  376. return result;
  377. }
  378. bool TextEditor::Render(const char* aTitle, bool aParentIsFocused, const ImVec2& aSize, bool aBorder)
  379. {
  380. if (mCursorPositionChanged)
  381. OnCursorPositionChanged();
  382. mCursorPositionChanged = false;
  383. ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(mPalette[(int)PaletteIndex::Background]));
  384. ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
  385. ImGui::BeginChild(aTitle, aSize, aBorder, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNavInputs);
  386. bool isFocused = ImGui::IsWindowFocused();
  387. HandleKeyboardInputs(aParentIsFocused);
  388. HandleMouseInputs();
  389. ColorizeInternal();
  390. Render(aParentIsFocused);
  391. ImGui::EndChild();
  392. ImGui::PopStyleVar();
  393. ImGui::PopStyleColor();
  394. return isFocused;
  395. }
  396. // ------------------------------------ //
  397. // ---------- Generic utils ----------- //
  398. // https://en.wikipedia.org/wiki/UTF-8
  399. // We assume that the char is a standalone character (<128) or a leading byte of an UTF-8 code sequence (non-10xxxxxx code)
  400. static int UTF8CharLength(char c)
  401. {
  402. if ((c & 0xFE) == 0xFC)
  403. return 6;
  404. if ((c & 0xFC) == 0xF8)
  405. return 5;
  406. if ((c & 0xF8) == 0xF0)
  407. return 4;
  408. else if ((c & 0xF0) == 0xE0)
  409. return 3;
  410. else if ((c & 0xE0) == 0xC0)
  411. return 2;
  412. return 1;
  413. }
  414. // "Borrowed" from ImGui source
  415. static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c)
  416. {
  417. if (c < 0x80)
  418. {
  419. buf[0] = (char)c;
  420. return 1;
  421. }
  422. if (c < 0x800)
  423. {
  424. if (buf_size < 2) return 0;
  425. buf[0] = (char)(0xc0 + (c >> 6));
  426. buf[1] = (char)(0x80 + (c & 0x3f));
  427. return 2;
  428. }
  429. if (c >= 0xdc00 && c < 0xe000)
  430. {
  431. return 0;
  432. }
  433. if (c >= 0xd800 && c < 0xdc00)
  434. {
  435. if (buf_size < 4) return 0;
  436. buf[0] = (char)(0xf0 + (c >> 18));
  437. buf[1] = (char)(0x80 + ((c >> 12) & 0x3f));
  438. buf[2] = (char)(0x80 + ((c >> 6) & 0x3f));
  439. buf[3] = (char)(0x80 + ((c) & 0x3f));
  440. return 4;
  441. }
  442. //else if (c < 0x10000)
  443. {
  444. if (buf_size < 3) return 0;
  445. buf[0] = (char)(0xe0 + (c >> 12));
  446. buf[1] = (char)(0x80 + ((c >> 6) & 0x3f));
  447. buf[2] = (char)(0x80 + ((c) & 0x3f));
  448. return 3;
  449. }
  450. }
  451. static inline bool CharIsWordChar(char ch)
  452. {
  453. int sizeInBytes = UTF8CharLength(ch);
  454. return sizeInBytes > 1 ||
  455. ch >= 'a' && ch <= 'z' ||
  456. ch >= 'A' && ch <= 'Z' ||
  457. ch >= '0' && ch <= '9' ||
  458. ch == '_';
  459. }
  460. // ------------------------------------ //
  461. // ------------- Internal ------------- //
  462. // ---------- Editor state functions --------- //
  463. void TextEditor::EditorState::AddCursor()
  464. {
  465. // vector is never resized to smaller size, mCurrentCursor points to last available cursor in vector
  466. mCurrentCursor++;
  467. mCursors.resize(mCurrentCursor + 1);
  468. mLastAddedCursor = mCurrentCursor;
  469. }
  470. int TextEditor::EditorState::GetLastAddedCursorIndex()
  471. {
  472. return mLastAddedCursor > mCurrentCursor ? 0 : mLastAddedCursor;
  473. }
  474. void TextEditor::EditorState::SortCursorsFromTopToBottom()
  475. {
  476. Coordinates lastAddedCursorPos = mCursors[GetLastAddedCursorIndex()].mInteractiveEnd;
  477. std::sort(mCursors.begin(), mCursors.begin() + (mCurrentCursor + 1), [](const Cursor& a, const Cursor& b) -> bool
  478. {
  479. return a.GetSelectionStart() < b.GetSelectionStart();
  480. });
  481. // update last added cursor index to be valid after sort
  482. for (int c = mCurrentCursor; c > -1; c--)
  483. if (mCursors[c].mInteractiveEnd == lastAddedCursorPos)
  484. mLastAddedCursor = c;
  485. }
  486. // ---------- Undo record functions --------- //
  487. TextEditor::UndoRecord::UndoRecord(const std::vector<UndoOperation>& aOperations,
  488. TextEditor::EditorState& aBefore, TextEditor::EditorState& aAfter)
  489. {
  490. mOperations = aOperations;
  491. mBefore = aBefore;
  492. mAfter = aAfter;
  493. for (const UndoOperation& o : mOperations)
  494. assert(o.mStart <= o.mEnd);
  495. }
  496. void TextEditor::UndoRecord::Undo(TextEditor* aEditor)
  497. {
  498. for (int i = mOperations.size() - 1; i > -1; i--)
  499. {
  500. const UndoOperation& operation = mOperations[i];
  501. if (!operation.mText.empty())
  502. {
  503. switch (operation.mType)
  504. {
  505. case UndoOperationType::Delete:
  506. {
  507. auto start = operation.mStart;
  508. aEditor->InsertTextAt(start, operation.mText.c_str());
  509. aEditor->Colorize(operation.mStart.mLine - 1, operation.mEnd.mLine - operation.mStart.mLine + 2);
  510. break;
  511. }
  512. case UndoOperationType::Add:
  513. {
  514. aEditor->DeleteRange(operation.mStart, operation.mEnd);
  515. aEditor->Colorize(operation.mStart.mLine - 1, operation.mEnd.mLine - operation.mStart.mLine + 2);
  516. break;
  517. }
  518. }
  519. }
  520. }
  521. aEditor->mState = mBefore;
  522. aEditor->EnsureCursorVisible();
  523. }
  524. void TextEditor::UndoRecord::Redo(TextEditor* aEditor)
  525. {
  526. for (int i = 0; i < mOperations.size(); i++)
  527. {
  528. const UndoOperation& operation = mOperations[i];
  529. if (!operation.mText.empty())
  530. {
  531. switch (operation.mType)
  532. {
  533. case UndoOperationType::Delete:
  534. {
  535. aEditor->DeleteRange(operation.mStart, operation.mEnd);
  536. aEditor->Colorize(operation.mStart.mLine - 1, operation.mEnd.mLine - operation.mStart.mLine + 1);
  537. break;
  538. }
  539. case UndoOperationType::Add:
  540. {
  541. auto start = operation.mStart;
  542. aEditor->InsertTextAt(start, operation.mText.c_str());
  543. aEditor->Colorize(operation.mStart.mLine - 1, operation.mEnd.mLine - operation.mStart.mLine + 1);
  544. break;
  545. }
  546. }
  547. }
  548. }
  549. aEditor->mState = mAfter;
  550. aEditor->EnsureCursorVisible();
  551. }
  552. // ---------- Text editor internal functions --------- //
  553. std::string TextEditor::GetText() const
  554. {
  555. auto lastLine = (int)mLines.size() - 1;
  556. auto lastLineLength = GetLineMaxColumn(lastLine);
  557. return GetText(Coordinates(), Coordinates(lastLine, lastLineLength));
  558. }
  559. std::string TextEditor::GetClipboardText() const
  560. {
  561. std::string result;
  562. for (int c = 0; c <= mState.mCurrentCursor; c++)
  563. {
  564. if (mState.mCursors[c].GetSelectionStart() < mState.mCursors[c].GetSelectionEnd())
  565. {
  566. if (result.length() != 0)
  567. result += '\n';
  568. result += GetText(mState.mCursors[c].GetSelectionStart(), mState.mCursors[c].GetSelectionEnd());
  569. }
  570. }
  571. return result;
  572. }
  573. std::string TextEditor::GetSelectedText(int aCursor) const
  574. {
  575. if (aCursor == -1)
  576. aCursor = mState.mCurrentCursor;
  577. return GetText(mState.mCursors[aCursor].GetSelectionStart(), mState.mCursors[aCursor].GetSelectionEnd());
  578. }
  579. void TextEditor::SetCursorPosition(const Coordinates& aPosition, int aCursor, bool aClearSelection)
  580. {
  581. if (aCursor == -1)
  582. aCursor = mState.mCurrentCursor;
  583. mCursorPositionChanged = true;
  584. if (aClearSelection)
  585. mState.mCursors[aCursor].mInteractiveStart = aPosition;
  586. if (mState.mCursors[aCursor].mInteractiveEnd != aPosition)
  587. {
  588. mState.mCursors[aCursor].mInteractiveEnd = aPosition;
  589. EnsureCursorVisible();
  590. }
  591. }
  592. int TextEditor::InsertTextAt(Coordinates& /* inout */ aWhere, const char* aValue)
  593. {
  594. assert(!mReadOnly);
  595. int cindex = GetCharacterIndexR(aWhere);
  596. int totalLines = 0;
  597. while (*aValue != '\0')
  598. {
  599. assert(!mLines.empty());
  600. if (*aValue == '\r')
  601. {
  602. // skip
  603. ++aValue;
  604. }
  605. else if (*aValue == '\n')
  606. {
  607. if (cindex < (int)mLines[aWhere.mLine].size())
  608. {
  609. auto& newLine = InsertLine(aWhere.mLine + 1);
  610. auto& line = mLines[aWhere.mLine];
  611. AddGlyphsToLine(aWhere.mLine + 1, 0, line.begin() + cindex, line.end());
  612. RemoveGlyphsFromLine(aWhere.mLine, cindex);
  613. }
  614. else
  615. {
  616. InsertLine(aWhere.mLine + 1);
  617. }
  618. ++aWhere.mLine;
  619. aWhere.mColumn = 0;
  620. cindex = 0;
  621. ++totalLines;
  622. ++aValue;
  623. }
  624. else
  625. {
  626. auto& line = mLines[aWhere.mLine];
  627. auto d = UTF8CharLength(*aValue);
  628. while (d-- > 0 && *aValue != '\0')
  629. AddGlyphToLine(aWhere.mLine, cindex++, Glyph(*aValue++, PaletteIndex::Default));
  630. aWhere.mColumn = GetCharacterColumn(aWhere.mLine, cindex);
  631. }
  632. }
  633. return totalLines;
  634. }
  635. void TextEditor::InsertTextAtCursor(const std::string& aValue, int aCursor)
  636. {
  637. InsertTextAtCursor(aValue.c_str(), aCursor);
  638. }
  639. void TextEditor::InsertTextAtCursor(const char* aValue, int aCursor)
  640. {
  641. if (aValue == nullptr)
  642. return;
  643. if (aCursor == -1)
  644. aCursor = mState.mCurrentCursor;
  645. auto pos = GetActualCursorCoordinates(aCursor);
  646. auto start = std::min(pos, mState.mCursors[aCursor].GetSelectionStart());
  647. int totalLines = pos.mLine - start.mLine;
  648. totalLines += InsertTextAt(pos, aValue);
  649. SetCursorPosition(pos, aCursor);
  650. Colorize(start.mLine - 1, totalLines + 2);
  651. }
  652. bool TextEditor::Move(int& aLine, int& aCharIndex, bool aLeft, bool aLockLine) const
  653. {
  654. // assumes given char index is not in the middle of utf8 sequence
  655. // char index can be line.length()
  656. // invalid line
  657. if (aLine >= mLines.size())
  658. return false;
  659. if (aLeft)
  660. {
  661. if (aCharIndex == 0)
  662. {
  663. if (aLockLine || aLine == 0)
  664. return false;
  665. aLine--;
  666. aCharIndex = mLines[aLine].size();
  667. }
  668. else
  669. {
  670. aCharIndex--;
  671. while (aCharIndex > 0 && IsUTFSequence(mLines[aLine][aCharIndex].mChar))
  672. aCharIndex--;
  673. }
  674. }
  675. else // right
  676. {
  677. if (aCharIndex == mLines[aLine].size())
  678. {
  679. if (aLockLine || aLine == mLines.size() - 1)
  680. return false;
  681. aLine++;
  682. aCharIndex = 0;
  683. }
  684. else
  685. {
  686. int seqLength = UTF8CharLength(mLines[aLine][aCharIndex].mChar);
  687. aCharIndex = std::min(aCharIndex + seqLength, (int)mLines[aLine].size());
  688. }
  689. }
  690. return true;
  691. }
  692. void TextEditor::MoveCharIndexAndColumn(int aLine, int& aCharIndex, int& aColumn) const
  693. {
  694. assert(aLine < mLines.size());
  695. assert(aCharIndex < mLines[aLine].size());
  696. char c = mLines[aLine][aCharIndex].mChar;
  697. aCharIndex += UTF8CharLength(c);
  698. if (c == '\t')
  699. aColumn = (aColumn / mTabSize) * mTabSize + mTabSize;
  700. else
  701. aColumn++;
  702. }
  703. void TextEditor::MoveCoords(Coordinates& aCoords, MoveDirection aDirection, bool aWordMode, int aLineCount) const
  704. {
  705. int charIndex = GetCharacterIndexR(aCoords);
  706. int lineIndex = aCoords.mLine;
  707. switch (aDirection)
  708. {
  709. case MoveDirection::Right:
  710. if (charIndex >= mLines[lineIndex].size())
  711. {
  712. if (lineIndex < mLines.size() - 1)
  713. {
  714. aCoords.mLine = std::max(0, std::min((int)mLines.size() - 1, lineIndex + 1));
  715. aCoords.mColumn = 0;
  716. }
  717. }
  718. else
  719. {
  720. Move(lineIndex, charIndex);
  721. int oneStepRightColumn = GetCharacterColumn(lineIndex, charIndex);
  722. if (aWordMode)
  723. {
  724. aCoords = FindWordEnd(aCoords);
  725. aCoords.mColumn = std::max(aCoords.mColumn, oneStepRightColumn);
  726. }
  727. else
  728. aCoords.mColumn = oneStepRightColumn;
  729. }
  730. break;
  731. case MoveDirection::Left:
  732. if (charIndex == 0)
  733. {
  734. if (lineIndex > 0)
  735. {
  736. aCoords.mLine = lineIndex - 1;
  737. aCoords.mColumn = GetLineMaxColumn(aCoords.mLine);
  738. }
  739. }
  740. else
  741. {
  742. Move(lineIndex, charIndex, true);
  743. aCoords.mColumn = GetCharacterColumn(lineIndex, charIndex);
  744. if (aWordMode)
  745. aCoords = FindWordStart(aCoords);
  746. }
  747. break;
  748. case MoveDirection::Up:
  749. aCoords.mLine = std::max(0, lineIndex - aLineCount);
  750. break;
  751. case MoveDirection::Down:
  752. aCoords.mLine = std::max(0, std::min((int)mLines.size() - 1, lineIndex + aLineCount));
  753. break;
  754. }
  755. }
  756. void TextEditor::MoveUp(int aAmount, bool aSelect)
  757. {
  758. for (int c = 0; c <= mState.mCurrentCursor; c++)
  759. {
  760. Coordinates newCoords = mState.mCursors[c].mInteractiveEnd;
  761. MoveCoords(newCoords, MoveDirection::Up, false, aAmount);
  762. SetCursorPosition(newCoords, c, !aSelect);
  763. }
  764. EnsureCursorVisible();
  765. }
  766. void TextEditor::MoveDown(int aAmount, bool aSelect)
  767. {
  768. for (int c = 0; c <= mState.mCurrentCursor; c++)
  769. {
  770. assert(mState.mCursors[c].mInteractiveEnd.mColumn >= 0);
  771. Coordinates newCoords = mState.mCursors[c].mInteractiveEnd;
  772. MoveCoords(newCoords, MoveDirection::Down, false, aAmount);
  773. SetCursorPosition(newCoords, c, !aSelect);
  774. }
  775. EnsureCursorVisible();
  776. }
  777. void TextEditor::MoveLeft(bool aSelect, bool aWordMode)
  778. {
  779. if (mLines.empty())
  780. return;
  781. if (AnyCursorHasSelection() && !aSelect && !aWordMode)
  782. {
  783. for (int c = 0; c <= mState.mCurrentCursor; c++)
  784. SetCursorPosition(mState.mCursors[c].GetSelectionStart(), c);
  785. }
  786. else
  787. {
  788. for (int c = 0; c <= mState.mCurrentCursor; c++)
  789. {
  790. Coordinates newCoords = mState.mCursors[c].mInteractiveEnd;
  791. MoveCoords(newCoords, MoveDirection::Left, aWordMode);
  792. SetCursorPosition(newCoords, c, !aSelect);
  793. }
  794. }
  795. EnsureCursorVisible();
  796. }
  797. void TextEditor::MoveRight(bool aSelect, bool aWordMode)
  798. {
  799. if (mLines.empty())
  800. return;
  801. if (AnyCursorHasSelection() && !aSelect && !aWordMode)
  802. {
  803. for (int c = 0; c <= mState.mCurrentCursor; c++)
  804. SetCursorPosition(mState.mCursors[c].GetSelectionEnd(), c);
  805. }
  806. else
  807. {
  808. for (int c = 0; c <= mState.mCurrentCursor; c++)
  809. {
  810. Coordinates newCoords = mState.mCursors[c].mInteractiveEnd;
  811. MoveCoords(newCoords, MoveDirection::Right, aWordMode);
  812. SetCursorPosition(newCoords, c, !aSelect);
  813. }
  814. }
  815. EnsureCursorVisible();
  816. }
  817. void TextEditor::MoveTop(bool aSelect)
  818. {
  819. SetCursorPosition(Coordinates(0, 0), mState.mCurrentCursor, !aSelect);
  820. }
  821. void TextEditor::TextEditor::MoveBottom(bool aSelect)
  822. {
  823. int maxLine = (int)mLines.size() - 1;
  824. Coordinates newPos = Coordinates(maxLine, GetLineMaxColumn(maxLine));
  825. SetCursorPosition(newPos, mState.mCurrentCursor, !aSelect);
  826. }
  827. void TextEditor::MoveHome(bool aSelect)
  828. {
  829. for (int c = 0; c <= mState.mCurrentCursor; c++)
  830. SetCursorPosition(Coordinates(mState.mCursors[c].mInteractiveEnd.mLine, 0), c, !aSelect);
  831. }
  832. void TextEditor::MoveEnd(bool aSelect)
  833. {
  834. for (int c = 0; c <= mState.mCurrentCursor; c++)
  835. {
  836. int lindex = mState.mCursors[c].mInteractiveEnd.mLine;
  837. SetCursorPosition(Coordinates(lindex, GetLineMaxColumn(lindex)), c, !aSelect);
  838. }
  839. }
  840. void TextEditor::EnterCharacter(ImWchar aChar, bool aShift)
  841. {
  842. assert(!mReadOnly);
  843. bool hasSelection = AnyCursorHasSelection();
  844. bool anyCursorHasMultilineSelection = false;
  845. for (int c = mState.mCurrentCursor; c > -1; c--)
  846. if (mState.mCursors[c].GetSelectionStart().mLine != mState.mCursors[c].GetSelectionEnd().mLine)
  847. {
  848. anyCursorHasMultilineSelection = true;
  849. break;
  850. }
  851. bool isIndentOperation = hasSelection && anyCursorHasMultilineSelection && aChar == '\t';
  852. if (isIndentOperation)
  853. {
  854. ChangeCurrentLinesIndentation(!aShift);
  855. return;
  856. }
  857. UndoRecord u;
  858. u.mBefore = mState;
  859. if (hasSelection)
  860. {
  861. for (int c = mState.mCurrentCursor; c > -1; c--)
  862. {
  863. u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].GetSelectionStart(), mState.mCursors[c].GetSelectionEnd(), UndoOperationType::Delete });
  864. DeleteSelection(c);
  865. }
  866. }
  867. std::vector<Coordinates> coords;
  868. for (int c = mState.mCurrentCursor; c > -1; c--) // order important here for typing \n in the same line at the same time
  869. {
  870. auto coord = GetActualCursorCoordinates(c);
  871. coords.push_back(coord);
  872. UndoOperation added;
  873. added.mType = UndoOperationType::Add;
  874. added.mStart = coord;
  875. assert(!mLines.empty());
  876. if (aChar == '\n')
  877. {
  878. InsertLine(coord.mLine + 1);
  879. auto& line = mLines[coord.mLine];
  880. auto& newLine = mLines[coord.mLine + 1];
  881. added.mText = "";
  882. added.mText += (char)aChar;
  883. if (mAutoIndent)
  884. for (int i = 0; i < line.size() && isascii(line[i].mChar) && isblank(line[i].mChar); ++i)
  885. {
  886. newLine.push_back(line[i]);
  887. added.mText += line[i].mChar;
  888. }
  889. const size_t whitespaceSize = newLine.size();
  890. auto cindex = GetCharacterIndexR(coord);
  891. AddGlyphsToLine(coord.mLine + 1, newLine.size(), line.begin() + cindex, line.end());
  892. RemoveGlyphsFromLine(coord.mLine, cindex);
  893. SetCursorPosition(Coordinates(coord.mLine + 1, GetCharacterColumn(coord.mLine + 1, (int)whitespaceSize)), c);
  894. }
  895. else
  896. {
  897. char buf[7];
  898. int e = ImTextCharToUtf8(buf, 7, aChar);
  899. if (e > 0)
  900. {
  901. buf[e] = '\0';
  902. auto& line = mLines[coord.mLine];
  903. auto cindex = GetCharacterIndexR(coord);
  904. if (mOverwrite && cindex < (int)line.size())
  905. {
  906. auto d = UTF8CharLength(line[cindex].mChar);
  907. UndoOperation removed;
  908. removed.mType = UndoOperationType::Delete;
  909. removed.mStart = mState.mCursors[c].mInteractiveEnd;
  910. removed.mEnd = Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex + d));
  911. while (d-- > 0 && cindex < (int)line.size())
  912. {
  913. removed.mText += line[cindex].mChar;
  914. RemoveGlyphsFromLine(coord.mLine, cindex, cindex + 1);
  915. }
  916. u.mOperations.push_back(removed);
  917. }
  918. for (auto p = buf; *p != '\0'; p++, ++cindex)
  919. AddGlyphToLine(coord.mLine, cindex, Glyph(*p, PaletteIndex::Default));
  920. added.mText = buf;
  921. SetCursorPosition(Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex)), c);
  922. }
  923. else
  924. continue;
  925. }
  926. added.mEnd = GetActualCursorCoordinates(c);
  927. u.mOperations.push_back(added);
  928. }
  929. u.mAfter = mState;
  930. AddUndo(u);
  931. for (const auto& coord : coords)
  932. Colorize(coord.mLine - 1, 3);
  933. EnsureCursorVisible();
  934. }
  935. void TextEditor::Backspace(bool aWordMode)
  936. {
  937. assert(!mReadOnly);
  938. if (mLines.empty())
  939. return;
  940. if (AnyCursorHasSelection())
  941. Delete(aWordMode);
  942. else
  943. {
  944. EditorState stateBeforeDeleting = mState;
  945. MoveLeft(true, aWordMode);
  946. if (!AllCursorsHaveSelection()) // can't do backspace if any cursor at {0,0}
  947. {
  948. if (AnyCursorHasSelection())
  949. MoveRight();
  950. return;
  951. }
  952. OnCursorPositionChanged(); // might combine cursors
  953. Delete(aWordMode, &stateBeforeDeleting);
  954. }
  955. }
  956. void TextEditor::Delete(bool aWordMode, const EditorState* aEditorState)
  957. {
  958. assert(!mReadOnly);
  959. if (mLines.empty())
  960. return;
  961. if (AnyCursorHasSelection())
  962. {
  963. UndoRecord u;
  964. u.mBefore = aEditorState == nullptr ? mState : *aEditorState;
  965. for (int c = mState.mCurrentCursor; c > -1; c--)
  966. {
  967. if (!mState.mCursors[c].HasSelection())
  968. continue;
  969. u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].GetSelectionStart(), mState.mCursors[c].GetSelectionEnd(), UndoOperationType::Delete });
  970. DeleteSelection(c);
  971. }
  972. u.mAfter = mState;
  973. AddUndo(u);
  974. }
  975. else
  976. {
  977. EditorState stateBeforeDeleting = mState;
  978. MoveRight(true, aWordMode);
  979. if (!AllCursorsHaveSelection()) // can't do delete if any cursor at end of last line
  980. {
  981. if (AnyCursorHasSelection())
  982. MoveLeft();
  983. return;
  984. }
  985. OnCursorPositionChanged(); // might combine cursors
  986. Delete(aWordMode, &stateBeforeDeleting);
  987. }
  988. }
  989. void TextEditor::SetSelection(Coordinates aStart, Coordinates aEnd, int aCursor)
  990. {
  991. if (aCursor == -1)
  992. aCursor = mState.mCurrentCursor;
  993. Coordinates minCoords = Coordinates(0, 0);
  994. int maxLine = (int)mLines.size() - 1;
  995. Coordinates maxCoords = Coordinates(maxLine, GetLineMaxColumn(maxLine));
  996. if (aStart < minCoords)
  997. aStart = minCoords;
  998. else if (aStart > maxCoords)
  999. aStart = maxCoords;
  1000. if (aEnd < minCoords)
  1001. aEnd = minCoords;
  1002. else if (aEnd > maxCoords)
  1003. aEnd = maxCoords;
  1004. mState.mCursors[aCursor].mInteractiveStart = aStart;
  1005. SetCursorPosition(aEnd, aCursor, false);
  1006. }
  1007. void TextEditor::SetSelection(int aStartLine, int aStartChar, int aEndLine, int aEndChar, int aCursor)
  1008. {
  1009. Coordinates startCoords = { aStartLine, GetCharacterColumn(aStartLine, aStartChar) };
  1010. Coordinates endCoords = { aEndLine, GetCharacterColumn(aEndLine, aEndChar) };
  1011. SetSelection(startCoords, endCoords, aCursor);
  1012. }
  1013. void TextEditor::SelectNextOccurrenceOf(const char* aText, int aTextSize, int aCursor, bool aCaseSensitive)
  1014. {
  1015. if (aCursor == -1)
  1016. aCursor = mState.mCurrentCursor;
  1017. Coordinates nextStart, nextEnd;
  1018. FindNextOccurrence(aText, aTextSize, mState.mCursors[aCursor].mInteractiveEnd, nextStart, nextEnd, aCaseSensitive);
  1019. SetSelection(nextStart, nextEnd, aCursor);
  1020. EnsureCursorVisible(aCursor, true);
  1021. }
  1022. void TextEditor::AddCursorForNextOccurrence(bool aCaseSensitive)
  1023. {
  1024. const Cursor& currentCursor = mState.mCursors[mState.GetLastAddedCursorIndex()];
  1025. if (currentCursor.GetSelectionStart() == currentCursor.GetSelectionEnd())
  1026. return;
  1027. std::string selectionText = GetText(currentCursor.GetSelectionStart(), currentCursor.GetSelectionEnd());
  1028. Coordinates nextStart, nextEnd;
  1029. if (!FindNextOccurrence(selectionText.c_str(), selectionText.length(), currentCursor.GetSelectionEnd(), nextStart, nextEnd, aCaseSensitive))
  1030. return;
  1031. mState.AddCursor();
  1032. SetSelection(nextStart, nextEnd, mState.mCurrentCursor);
  1033. mState.SortCursorsFromTopToBottom();
  1034. MergeCursorsIfPossible();
  1035. EnsureCursorVisible(-1, true);
  1036. }
  1037. bool TextEditor::FindNextOccurrence(const char* aText, int aTextSize, const Coordinates& aFrom, Coordinates& outStart, Coordinates& outEnd, bool aCaseSensitive)
  1038. {
  1039. assert(aTextSize > 0);
  1040. bool fmatches = false;
  1041. int fline, ifline;
  1042. int findex, ifindex;
  1043. ifline = fline = aFrom.mLine;
  1044. ifindex = findex = GetCharacterIndexR(aFrom);
  1045. while (true)
  1046. {
  1047. bool matches;
  1048. { // match function
  1049. int lineOffset = 0;
  1050. int currentCharIndex = findex;
  1051. int i = 0;
  1052. for (; i < aTextSize; i++)
  1053. {
  1054. if (currentCharIndex == mLines[fline + lineOffset].size())
  1055. {
  1056. if (aText[i] == '\n' && fline + lineOffset + 1 < mLines.size())
  1057. {
  1058. currentCharIndex = 0;
  1059. lineOffset++;
  1060. }
  1061. else
  1062. break;
  1063. }
  1064. else
  1065. {
  1066. char toCompareA = mLines[fline + lineOffset][currentCharIndex].mChar;
  1067. char toCompareB = aText[i];
  1068. toCompareA = (!aCaseSensitive && toCompareA >= 'A' && toCompareA <= 'Z') ? toCompareA - 'A' + 'a' : toCompareA;
  1069. toCompareB = (!aCaseSensitive && toCompareB >= 'A' && toCompareB <= 'Z') ? toCompareB - 'A' + 'a' : toCompareB;
  1070. if (toCompareA != toCompareB)
  1071. break;
  1072. else
  1073. currentCharIndex++;
  1074. }
  1075. }
  1076. matches = i == aTextSize;
  1077. if (matches)
  1078. {
  1079. outStart = { fline, GetCharacterColumn(fline, findex) };
  1080. outEnd = { fline + lineOffset, GetCharacterColumn(fline + lineOffset, currentCharIndex) };
  1081. return true;
  1082. }
  1083. }
  1084. // move forward
  1085. if (findex == mLines[fline].size()) // need to consider line breaks
  1086. {
  1087. if (fline == mLines.size() - 1)
  1088. {
  1089. fline = 0;
  1090. findex = 0;
  1091. }
  1092. else
  1093. {
  1094. fline++;
  1095. findex = 0;
  1096. }
  1097. }
  1098. else
  1099. findex++;
  1100. // detect complete scan
  1101. if (findex == ifindex && fline == ifline)
  1102. return false;
  1103. }
  1104. return false;
  1105. }
  1106. bool TextEditor::FindMatchingBracket(int aLine, int aCharIndex, Coordinates& out)
  1107. {
  1108. if (aLine > mLines.size() - 1)
  1109. return false;
  1110. int maxCharIndex = mLines[aLine].size() - 1;
  1111. if (aCharIndex > maxCharIndex)
  1112. return false;
  1113. int currentLine = aLine;
  1114. int currentCharIndex = aCharIndex;
  1115. int counter = 1;
  1116. if (CLOSE_TO_OPEN_CHAR.find(mLines[aLine][aCharIndex].mChar) != CLOSE_TO_OPEN_CHAR.end())
  1117. {
  1118. char closeChar = mLines[aLine][aCharIndex].mChar;
  1119. char openChar = CLOSE_TO_OPEN_CHAR.at(closeChar);
  1120. while (Move(currentLine, currentCharIndex, true))
  1121. {
  1122. if (currentCharIndex < mLines[currentLine].size())
  1123. {
  1124. char currentChar = mLines[currentLine][currentCharIndex].mChar;
  1125. if (currentChar == openChar)
  1126. {
  1127. counter--;
  1128. if (counter == 0)
  1129. {
  1130. out = { currentLine, GetCharacterColumn(currentLine, currentCharIndex) };
  1131. return true;
  1132. }
  1133. }
  1134. else if (currentChar == closeChar)
  1135. counter++;
  1136. }
  1137. }
  1138. }
  1139. else if (OPEN_TO_CLOSE_CHAR.find(mLines[aLine][aCharIndex].mChar) != OPEN_TO_CLOSE_CHAR.end())
  1140. {
  1141. char openChar = mLines[aLine][aCharIndex].mChar;
  1142. char closeChar = OPEN_TO_CLOSE_CHAR.at(openChar);
  1143. while (Move(currentLine, currentCharIndex))
  1144. {
  1145. if (currentCharIndex < mLines[currentLine].size())
  1146. {
  1147. char currentChar = mLines[currentLine][currentCharIndex].mChar;
  1148. if (currentChar == closeChar)
  1149. {
  1150. counter--;
  1151. if (counter == 0)
  1152. {
  1153. out = { currentLine, GetCharacterColumn(currentLine, currentCharIndex) };
  1154. return true;
  1155. }
  1156. }
  1157. else if (currentChar == openChar)
  1158. counter++;
  1159. }
  1160. }
  1161. }
  1162. return false;
  1163. }
  1164. void TextEditor::ChangeCurrentLinesIndentation(bool aIncrease)
  1165. {
  1166. assert(!mReadOnly);
  1167. UndoRecord u;
  1168. u.mBefore = mState;
  1169. for (int c = mState.mCurrentCursor; c > -1; c--)
  1170. {
  1171. for (int currentLine = mState.mCursors[c].GetSelectionEnd().mLine; currentLine >= mState.mCursors[c].GetSelectionStart().mLine; currentLine--)
  1172. {
  1173. if (Coordinates{ currentLine, 0 } == mState.mCursors[c].GetSelectionEnd() && mState.mCursors[c].GetSelectionEnd() != mState.mCursors[c].GetSelectionStart()) // when selection ends at line start
  1174. continue;
  1175. if (aIncrease)
  1176. {
  1177. if (mLines[currentLine].size() > 0)
  1178. {
  1179. Coordinates lineStart = { currentLine, 0 };
  1180. Coordinates insertionEnd = lineStart;
  1181. InsertTextAt(insertionEnd, "\t"); // sets insertion end
  1182. u.mOperations.push_back({ "\t", lineStart, insertionEnd, UndoOperationType::Add });
  1183. Colorize(lineStart.mLine, 1);
  1184. }
  1185. }
  1186. else
  1187. {
  1188. Coordinates start = { currentLine, 0 };
  1189. Coordinates end = { currentLine, mTabSize };
  1190. int charIndex = GetCharacterIndexL(end) - 1;
  1191. while (charIndex > -1 && (mLines[currentLine][charIndex].mChar == ' ' || mLines[currentLine][charIndex].mChar == '\t')) charIndex--;
  1192. bool onlySpaceCharactersFound = charIndex == -1;
  1193. if (onlySpaceCharactersFound)
  1194. {
  1195. u.mOperations.push_back({ GetText(start, end), start, end, UndoOperationType::Delete });
  1196. DeleteRange(start, end);
  1197. Colorize(currentLine, 1);
  1198. }
  1199. }
  1200. }
  1201. }
  1202. if (u.mOperations.size() > 0)
  1203. AddUndo(u);
  1204. }
  1205. void TextEditor::MoveUpCurrentLines()
  1206. {
  1207. assert(!mReadOnly);
  1208. UndoRecord u;
  1209. u.mBefore = mState;
  1210. std::set<int> affectedLines;
  1211. int minLine = -1;
  1212. int maxLine = -1;
  1213. for (int c = mState.mCurrentCursor; c > -1; c--) // cursors are expected to be sorted from top to bottom
  1214. {
  1215. for (int currentLine = mState.mCursors[c].GetSelectionEnd().mLine; currentLine >= mState.mCursors[c].GetSelectionStart().mLine; currentLine--)
  1216. {
  1217. if (Coordinates{ currentLine, 0 } == mState.mCursors[c].GetSelectionEnd() && mState.mCursors[c].GetSelectionEnd() != mState.mCursors[c].GetSelectionStart()) // when selection ends at line start
  1218. continue;
  1219. affectedLines.insert(currentLine);
  1220. minLine = minLine == -1 ? currentLine : (currentLine < minLine ? currentLine : minLine);
  1221. maxLine = maxLine == -1 ? currentLine : (currentLine > maxLine ? currentLine : maxLine);
  1222. }
  1223. }
  1224. if (minLine == 0) // can't move up anymore
  1225. return;
  1226. Coordinates start = { minLine - 1, 0 };
  1227. Coordinates end = { maxLine, GetLineMaxColumn(maxLine) };
  1228. u.mOperations.push_back({ GetText(start, end), start, end, UndoOperationType::Delete });
  1229. for (int line : affectedLines) // lines should be sorted here
  1230. std::swap(mLines[line - 1], mLines[line]);
  1231. for (int c = mState.mCurrentCursor; c > -1; c--)
  1232. {
  1233. mState.mCursors[c].mInteractiveStart.mLine -= 1;
  1234. mState.mCursors[c].mInteractiveEnd.mLine -= 1;
  1235. // no need to set mCursorPositionChanged as cursors will remain sorted
  1236. }
  1237. end = { maxLine, GetLineMaxColumn(maxLine) }; // this line is swapped with line above, need to find new max column
  1238. u.mOperations.push_back({ GetText(start, end), start, end, UndoOperationType::Add });
  1239. u.mAfter = mState;
  1240. AddUndo(u);
  1241. }
  1242. void TextEditor::MoveDownCurrentLines()
  1243. {
  1244. assert(!mReadOnly);
  1245. UndoRecord u;
  1246. u.mBefore = mState;
  1247. std::set<int> affectedLines;
  1248. int minLine = -1;
  1249. int maxLine = -1;
  1250. for (int c = 0; c <= mState.mCurrentCursor; c++) // cursors are expected to be sorted from top to bottom
  1251. {
  1252. for (int currentLine = mState.mCursors[c].GetSelectionEnd().mLine; currentLine >= mState.mCursors[c].GetSelectionStart().mLine; currentLine--)
  1253. {
  1254. if (Coordinates{ currentLine, 0 } == mState.mCursors[c].GetSelectionEnd() && mState.mCursors[c].GetSelectionEnd() != mState.mCursors[c].GetSelectionStart()) // when selection ends at line start
  1255. continue;
  1256. affectedLines.insert(currentLine);
  1257. minLine = minLine == -1 ? currentLine : (currentLine < minLine ? currentLine : minLine);
  1258. maxLine = maxLine == -1 ? currentLine : (currentLine > maxLine ? currentLine : maxLine);
  1259. }
  1260. }
  1261. if (maxLine == mLines.size() - 1) // can't move down anymore
  1262. return;
  1263. Coordinates start = { minLine, 0 };
  1264. Coordinates end = { maxLine + 1, GetLineMaxColumn(maxLine + 1)};
  1265. u.mOperations.push_back({ GetText(start, end), start, end, UndoOperationType::Delete });
  1266. std::set<int>::reverse_iterator rit;
  1267. for (rit = affectedLines.rbegin(); rit != affectedLines.rend(); rit++) // lines should be sorted here
  1268. std::swap(mLines[*rit + 1], mLines[*rit]);
  1269. for (int c = mState.mCurrentCursor; c > -1; c--)
  1270. {
  1271. mState.mCursors[c].mInteractiveStart.mLine += 1;
  1272. mState.mCursors[c].mInteractiveEnd.mLine += 1;
  1273. // no need to set mCursorPositionChanged as cursors will remain sorted
  1274. }
  1275. end = { maxLine + 1, GetLineMaxColumn(maxLine + 1) }; // this line is swapped with line below, need to find new max column
  1276. u.mOperations.push_back({ GetText(start, end), start, end, UndoOperationType::Add });
  1277. u.mAfter = mState;
  1278. AddUndo(u);
  1279. }
  1280. void TextEditor::ToggleLineComment()
  1281. {
  1282. assert(!mReadOnly);
  1283. if (mLanguageDefinition == nullptr)
  1284. return;
  1285. const std::string& commentString = mLanguageDefinition->mSingleLineComment;
  1286. UndoRecord u;
  1287. u.mBefore = mState;
  1288. bool shouldAddComment = false;
  1289. std::unordered_set<int> affectedLines;
  1290. for (int c = mState.mCurrentCursor; c > -1; c--)
  1291. {
  1292. for (int currentLine = mState.mCursors[c].GetSelectionEnd().mLine; currentLine >= mState.mCursors[c].GetSelectionStart().mLine; currentLine--)
  1293. {
  1294. if (Coordinates{ currentLine, 0 } == mState.mCursors[c].GetSelectionEnd() && mState.mCursors[c].GetSelectionEnd() != mState.mCursors[c].GetSelectionStart()) // when selection ends at line start
  1295. continue;
  1296. affectedLines.insert(currentLine);
  1297. int currentIndex = 0;
  1298. while (currentIndex < mLines[currentLine].size() && (mLines[currentLine][currentIndex].mChar == ' ' || mLines[currentLine][currentIndex].mChar == '\t')) currentIndex++;
  1299. if (currentIndex == mLines[currentLine].size())
  1300. continue;
  1301. int i = 0;
  1302. while (i < commentString.length() && currentIndex + i < mLines[currentLine].size() && mLines[currentLine][currentIndex + i].mChar == commentString[i]) i++;
  1303. bool matched = i == commentString.length();
  1304. shouldAddComment |= !matched;
  1305. }
  1306. }
  1307. if (shouldAddComment)
  1308. {
  1309. for (int currentLine : affectedLines) // order doesn't matter as changes are not multiline
  1310. {
  1311. Coordinates lineStart = { currentLine, 0 };
  1312. Coordinates insertionEnd = lineStart;
  1313. InsertTextAt(insertionEnd, (commentString + ' ').c_str()); // sets insertion end
  1314. u.mOperations.push_back({ (commentString + ' ') , lineStart, insertionEnd, UndoOperationType::Add });
  1315. Colorize(lineStart.mLine, 1);
  1316. }
  1317. }
  1318. else
  1319. {
  1320. for (int currentLine : affectedLines) // order doesn't matter as changes are not multiline
  1321. {
  1322. int currentIndex = 0;
  1323. while (currentIndex < mLines[currentLine].size() && (mLines[currentLine][currentIndex].mChar == ' ' || mLines[currentLine][currentIndex].mChar == '\t')) currentIndex++;
  1324. if (currentIndex == mLines[currentLine].size())
  1325. continue;
  1326. int i = 0;
  1327. while (i < commentString.length() && currentIndex + i < mLines[currentLine].size() && mLines[currentLine][currentIndex + i].mChar == commentString[i]) i++;
  1328. bool matched = i == commentString.length();
  1329. assert(matched);
  1330. if (currentIndex + i < mLines[currentLine].size() && mLines[currentLine][currentIndex + i].mChar == ' ')
  1331. i++;
  1332. Coordinates start = { currentLine, GetCharacterColumn(currentLine, currentIndex) };
  1333. Coordinates end = { currentLine, GetCharacterColumn(currentLine, currentIndex + i) };
  1334. u.mOperations.push_back({ GetText(start, end) , start, end, UndoOperationType::Delete});
  1335. DeleteRange(start, end);
  1336. Colorize(currentLine, 1);
  1337. }
  1338. }
  1339. u.mAfter = mState;
  1340. AddUndo(u);
  1341. }
  1342. void TextEditor::RemoveCurrentLines()
  1343. {
  1344. UndoRecord u;
  1345. u.mBefore = mState;
  1346. if (AnyCursorHasSelection())
  1347. {
  1348. for (int c = mState.mCurrentCursor; c > -1; c--)
  1349. {
  1350. if (!mState.mCursors[c].HasSelection())
  1351. continue;
  1352. u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].GetSelectionStart(), mState.mCursors[c].GetSelectionEnd(), UndoOperationType::Delete });
  1353. DeleteSelection(c);
  1354. }
  1355. }
  1356. MoveHome();
  1357. OnCursorPositionChanged(); // might combine cursors
  1358. for (int c = mState.mCurrentCursor; c > -1; c--)
  1359. {
  1360. int currentLine = mState.mCursors[c].mInteractiveEnd.mLine;
  1361. int nextLine = currentLine + 1;
  1362. int prevLine = currentLine - 1;
  1363. Coordinates toDeleteStart, toDeleteEnd;
  1364. if (mLines.size() > nextLine) // next line exists
  1365. {
  1366. toDeleteStart = Coordinates(currentLine, 0);
  1367. toDeleteEnd = Coordinates(nextLine, 0);
  1368. SetCursorPosition({ mState.mCursors[c].mInteractiveEnd.mLine, 0 }, c);
  1369. }
  1370. else if (prevLine > -1) // previous line exists
  1371. {
  1372. toDeleteStart = Coordinates(prevLine, GetLineMaxColumn(prevLine));
  1373. toDeleteEnd = Coordinates(currentLine, GetLineMaxColumn(currentLine));
  1374. SetCursorPosition({ prevLine, 0 }, c);
  1375. }
  1376. else
  1377. {
  1378. toDeleteStart = Coordinates(currentLine, 0);
  1379. toDeleteEnd = Coordinates(currentLine, GetLineMaxColumn(currentLine));
  1380. SetCursorPosition({ currentLine, 0 }, c);
  1381. }
  1382. u.mOperations.push_back({ GetText(toDeleteStart, toDeleteEnd), toDeleteStart, toDeleteEnd, UndoOperationType::Delete });
  1383. std::unordered_set<int> handledCursors = { c };
  1384. if (toDeleteStart.mLine != toDeleteEnd.mLine)
  1385. RemoveLine(currentLine, &handledCursors);
  1386. else
  1387. DeleteRange(toDeleteStart, toDeleteEnd);
  1388. }
  1389. u.mAfter = mState;
  1390. AddUndo(u);
  1391. }
  1392. float TextEditor::TextDistanceToLineStart(const Coordinates& aFrom, bool aSanitizeCoords) const
  1393. {
  1394. if (aSanitizeCoords)
  1395. return SanitizeCoordinates(aFrom).mColumn * mCharAdvance.x;
  1396. else
  1397. return aFrom.mColumn * mCharAdvance.x;
  1398. }
  1399. void TextEditor::EnsureCursorVisible(int aCursor, bool aStartToo)
  1400. {
  1401. if (aCursor == -1)
  1402. aCursor = mState.GetLastAddedCursorIndex();
  1403. mEnsureCursorVisible = aCursor;
  1404. mEnsureCursorVisibleStartToo = aStartToo;
  1405. return;
  1406. }
  1407. TextEditor::Coordinates TextEditor::SanitizeCoordinates(const Coordinates& aValue) const
  1408. {
  1409. auto line = aValue.mLine;
  1410. auto column = aValue.mColumn;
  1411. if (line >= (int) mLines.size())
  1412. {
  1413. if (mLines.empty())
  1414. {
  1415. line = 0;
  1416. column = 0;
  1417. }
  1418. else
  1419. {
  1420. line = (int) mLines.size() - 1;
  1421. column = GetLineMaxColumn(line);
  1422. }
  1423. return Coordinates(line, column);
  1424. }
  1425. else
  1426. {
  1427. column = mLines.empty() ? 0 : GetLineMaxColumn(line, column);
  1428. return Coordinates(line, column);
  1429. }
  1430. }
  1431. TextEditor::Coordinates TextEditor::GetActualCursorCoordinates(int aCursor, bool aStart) const
  1432. {
  1433. if (aCursor == -1)
  1434. return SanitizeCoordinates(aStart ? mState.mCursors[mState.mCurrentCursor].mInteractiveStart : mState.mCursors[mState.mCurrentCursor].mInteractiveEnd);
  1435. else
  1436. return SanitizeCoordinates(aStart ? mState.mCursors[aCursor].mInteractiveStart : mState.mCursors[aCursor].mInteractiveEnd);
  1437. }
  1438. TextEditor::Coordinates TextEditor::ScreenPosToCoordinates(const ImVec2& aPosition, bool aInsertionMode, bool* isOverLineNumber) const
  1439. {
  1440. ImVec2 origin = ImGui::GetCursorScreenPos();
  1441. ImVec2 local(aPosition.x - origin.x + 3.0f, aPosition.y - origin.y);
  1442. if (isOverLineNumber != nullptr)
  1443. *isOverLineNumber = local.x < mTextStart;
  1444. Coordinates out = {
  1445. Max(0, (int)floor(local.y / mCharAdvance.y)),
  1446. Max(0, (int)floor((local.x - mTextStart) / mCharAdvance.x))
  1447. };
  1448. int charIndex = GetCharacterIndexL(out);
  1449. if (charIndex > -1 && charIndex < mLines[out.mLine].size() && mLines[out.mLine][charIndex].mChar == '\t')
  1450. {
  1451. int columnToLeft = GetCharacterColumn(out.mLine, charIndex);
  1452. int columnToRight = GetCharacterColumn(out.mLine, GetCharacterIndexR(out));
  1453. if (out.mColumn - columnToLeft < columnToRight - out.mColumn)
  1454. out.mColumn = columnToLeft;
  1455. else
  1456. out.mColumn = columnToRight;
  1457. }
  1458. else
  1459. out.mColumn = Max(0, (int)floor((local.x - mTextStart + POS_TO_COORDS_COLUMN_OFFSET * mCharAdvance.x) / mCharAdvance.x));
  1460. return SanitizeCoordinates(out);
  1461. }
  1462. TextEditor::Coordinates TextEditor::FindWordStart(const Coordinates& aFrom) const
  1463. {
  1464. if (aFrom.mLine >= (int)mLines.size())
  1465. return aFrom;
  1466. int lineIndex = aFrom.mLine;
  1467. auto& line = mLines[lineIndex];
  1468. int charIndex = GetCharacterIndexL(aFrom);
  1469. if (charIndex > (int)line.size() || line.size() == 0)
  1470. return aFrom;
  1471. if (charIndex == (int)line.size())
  1472. charIndex--;
  1473. bool initialIsWordChar = CharIsWordChar(line[charIndex].mChar);
  1474. bool initialIsSpace = isspace(line[charIndex].mChar);
  1475. char initialChar = line[charIndex].mChar;
  1476. while (Move(lineIndex, charIndex, true, true))
  1477. {
  1478. bool isWordChar = CharIsWordChar(line[charIndex].mChar);
  1479. bool isSpace = isspace(line[charIndex].mChar);
  1480. if (initialIsSpace && !isSpace ||
  1481. initialIsWordChar && !isWordChar ||
  1482. !initialIsWordChar && !initialIsSpace && initialChar != line[charIndex].mChar)
  1483. {
  1484. Move(lineIndex, charIndex, false, true); // one step to the right
  1485. break;
  1486. }
  1487. }
  1488. return { aFrom.mLine, GetCharacterColumn(aFrom.mLine, charIndex) };
  1489. }
  1490. TextEditor::Coordinates TextEditor::FindWordEnd(const Coordinates& aFrom) const
  1491. {
  1492. if (aFrom.mLine >= (int)mLines.size())
  1493. return aFrom;
  1494. int lineIndex = aFrom.mLine;
  1495. auto& line = mLines[lineIndex];
  1496. auto charIndex = GetCharacterIndexL(aFrom);
  1497. if (charIndex >= (int)line.size())
  1498. return aFrom;
  1499. bool initialIsWordChar = CharIsWordChar(line[charIndex].mChar);
  1500. bool initialIsSpace = isspace(line[charIndex].mChar);
  1501. char initialChar = line[charIndex].mChar;
  1502. while (Move(lineIndex, charIndex, false, true))
  1503. {
  1504. if (charIndex == line.size())
  1505. break;
  1506. bool isWordChar = CharIsWordChar(line[charIndex].mChar);
  1507. bool isSpace = isspace(line[charIndex].mChar);
  1508. if (initialIsSpace && !isSpace ||
  1509. initialIsWordChar && !isWordChar ||
  1510. !initialIsWordChar && !initialIsSpace && initialChar != line[charIndex].mChar)
  1511. break;
  1512. }
  1513. return { lineIndex, GetCharacterColumn(aFrom.mLine, charIndex) };
  1514. }
  1515. int TextEditor::GetCharacterIndexL(const Coordinates& aCoords) const
  1516. {
  1517. if (aCoords.mLine >= mLines.size())
  1518. return -1;
  1519. auto& line = mLines[aCoords.mLine];
  1520. int c = 0;
  1521. int i = 0;
  1522. int tabCoordsLeft = 0;
  1523. for (; i < line.size() && c < aCoords.mColumn;)
  1524. {
  1525. if (line[i].mChar == '\t')
  1526. {
  1527. if (tabCoordsLeft == 0)
  1528. tabCoordsLeft = TabSizeAtColumn(c);
  1529. if (tabCoordsLeft > 0)
  1530. tabCoordsLeft--;
  1531. c++;
  1532. }
  1533. else
  1534. ++c;
  1535. if (tabCoordsLeft == 0)
  1536. i += UTF8CharLength(line[i].mChar);
  1537. }
  1538. return i;
  1539. }
  1540. int TextEditor::GetCharacterIndexR(const Coordinates& aCoords) const
  1541. {
  1542. if (aCoords.mLine >= mLines.size())
  1543. return -1;
  1544. int c = 0;
  1545. int i = 0;
  1546. for (; i < mLines[aCoords.mLine].size() && c < aCoords.mColumn;)
  1547. MoveCharIndexAndColumn(aCoords.mLine, i, c);
  1548. return i;
  1549. }
  1550. int TextEditor::GetCharacterColumn(int aLine, int aIndex) const
  1551. {
  1552. if (aLine >= mLines.size())
  1553. return 0;
  1554. int c = 0;
  1555. int i = 0;
  1556. while (i < aIndex && i < mLines[aLine].size())
  1557. MoveCharIndexAndColumn(aLine, i, c);
  1558. return c;
  1559. }
  1560. int TextEditor::GetFirstVisibleCharacterIndex(int aLine) const
  1561. {
  1562. if (aLine >= mLines.size())
  1563. return 0;
  1564. int c = 0;
  1565. int i = 0;
  1566. while (c < mFirstVisibleColumn && i < mLines[aLine].size())
  1567. MoveCharIndexAndColumn(aLine, i, c);
  1568. if (c > mFirstVisibleColumn)
  1569. i--;
  1570. return i;
  1571. }
  1572. int TextEditor::GetLineMaxColumn(int aLine, int aLimit) const
  1573. {
  1574. if (aLine >= mLines.size())
  1575. return 0;
  1576. int c = 0;
  1577. if (aLimit == -1)
  1578. {
  1579. for (int i = 0; i < mLines[aLine].size(); )
  1580. MoveCharIndexAndColumn(aLine, i, c);
  1581. }
  1582. else
  1583. {
  1584. for (int i = 0; i < mLines[aLine].size(); )
  1585. {
  1586. MoveCharIndexAndColumn(aLine, i, c);
  1587. if (c > aLimit)
  1588. return aLimit;
  1589. }
  1590. }
  1591. return c;
  1592. }
  1593. TextEditor::Line& TextEditor::InsertLine(int aIndex)
  1594. {
  1595. assert(!mReadOnly);
  1596. auto& result = *mLines.insert(mLines.begin() + aIndex, Line());
  1597. for (int c = 0; c <= mState.mCurrentCursor; c++) // handle multiple cursors
  1598. {
  1599. if (mState.mCursors[c].mInteractiveEnd.mLine >= aIndex)
  1600. SetCursorPosition({ mState.mCursors[c].mInteractiveEnd.mLine + 1, mState.mCursors[c].mInteractiveEnd.mColumn }, c);
  1601. }
  1602. return result;
  1603. }
  1604. void TextEditor::RemoveLine(int aIndex, const std::unordered_set<int>* aHandledCursors)
  1605. {
  1606. assert(!mReadOnly);
  1607. assert(mLines.size() > 1);
  1608. mLines.erase(mLines.begin() + aIndex);
  1609. assert(!mLines.empty());
  1610. // handle multiple cursors
  1611. for (int c = 0; c <= mState.mCurrentCursor; c++)
  1612. {
  1613. if (mState.mCursors[c].mInteractiveEnd.mLine >= aIndex)
  1614. {
  1615. if (aHandledCursors == nullptr || aHandledCursors->find(c) == aHandledCursors->end()) // move up if has not been handled already
  1616. SetCursorPosition({ mState.mCursors[c].mInteractiveEnd.mLine - 1, mState.mCursors[c].mInteractiveEnd.mColumn }, c);
  1617. }
  1618. }
  1619. }
  1620. void TextEditor::RemoveLines(int aStart, int aEnd)
  1621. {
  1622. assert(!mReadOnly);
  1623. assert(aEnd >= aStart);
  1624. assert(mLines.size() > (size_t)(aEnd - aStart));
  1625. mLines.erase(mLines.begin() + aStart, mLines.begin() + aEnd);
  1626. assert(!mLines.empty());
  1627. // handle multiple cursors
  1628. for (int c = 0; c <= mState.mCurrentCursor; c++)
  1629. {
  1630. if (mState.mCursors[c].mInteractiveEnd.mLine >= aStart)
  1631. {
  1632. int targetLine = mState.mCursors[c].mInteractiveEnd.mLine - (aEnd - aStart);
  1633. targetLine = targetLine < 0 ? 0 : targetLine;
  1634. SetCursorPosition({ targetLine , mState.mCursors[c].mInteractiveEnd.mColumn }, c);
  1635. }
  1636. }
  1637. }
  1638. void TextEditor::DeleteRange(const Coordinates& aStart, const Coordinates& aEnd)
  1639. {
  1640. assert(aEnd >= aStart);
  1641. assert(!mReadOnly);
  1642. if (aEnd == aStart)
  1643. return;
  1644. auto start = GetCharacterIndexL(aStart);
  1645. auto end = GetCharacterIndexR(aEnd);
  1646. if (aStart.mLine == aEnd.mLine)
  1647. {
  1648. auto n = GetLineMaxColumn(aStart.mLine);
  1649. if (aEnd.mColumn >= n)
  1650. RemoveGlyphsFromLine(aStart.mLine, start); // from start to end of line
  1651. else
  1652. RemoveGlyphsFromLine(aStart.mLine, start, end);
  1653. }
  1654. else
  1655. {
  1656. RemoveGlyphsFromLine(aStart.mLine, start); // from start to end of line
  1657. RemoveGlyphsFromLine(aEnd.mLine, 0, end);
  1658. auto& firstLine = mLines[aStart.mLine];
  1659. auto& lastLine = mLines[aEnd.mLine];
  1660. if (aStart.mLine < aEnd.mLine)
  1661. {
  1662. AddGlyphsToLine(aStart.mLine, firstLine.size(), lastLine.begin(), lastLine.end());
  1663. for (int c = 0; c <= mState.mCurrentCursor; c++) // move up cursors in line that is being moved up
  1664. {
  1665. if (mState.mCursors[c].mInteractiveEnd.mLine > aEnd.mLine)
  1666. break;
  1667. else if (mState.mCursors[c].mInteractiveEnd.mLine != aEnd.mLine)
  1668. continue;
  1669. int otherCursorEndCharIndex = GetCharacterIndexR(mState.mCursors[c].mInteractiveEnd);
  1670. int otherCursorStartCharIndex = GetCharacterIndexR(mState.mCursors[c].mInteractiveStart);
  1671. int otherCursorNewEndCharIndex = GetCharacterIndexR(aStart) + otherCursorEndCharIndex;
  1672. int otherCursorNewStartCharIndex = GetCharacterIndexR(aStart) + otherCursorStartCharIndex;
  1673. auto targetEndCoords = Coordinates(aStart.mLine, GetCharacterColumn(aStart.mLine, otherCursorNewEndCharIndex));
  1674. auto targetStartCoords = Coordinates(aStart.mLine, GetCharacterColumn(aStart.mLine, otherCursorNewStartCharIndex));
  1675. SetCursorPosition(targetStartCoords, c, true);
  1676. SetCursorPosition(targetEndCoords, c, false);
  1677. }
  1678. RemoveLines(aStart.mLine + 1, aEnd.mLine + 1);
  1679. }
  1680. }
  1681. }
  1682. void TextEditor::DeleteSelection(int aCursor)
  1683. {
  1684. if (aCursor == -1)
  1685. aCursor = mState.mCurrentCursor;
  1686. if (mState.mCursors[aCursor].GetSelectionEnd() == mState.mCursors[aCursor].GetSelectionStart())
  1687. return;
  1688. DeleteRange(mState.mCursors[aCursor].GetSelectionStart(), mState.mCursors[aCursor].GetSelectionEnd());
  1689. SetCursorPosition(mState.mCursors[aCursor].GetSelectionStart(), aCursor);
  1690. Colorize(mState.mCursors[aCursor].GetSelectionStart().mLine, 1);
  1691. }
  1692. void TextEditor::RemoveGlyphsFromLine(int aLine, int aStartChar, int aEndChar)
  1693. {
  1694. int column = GetCharacterColumn(aLine, aStartChar);
  1695. auto& line = mLines[aLine];
  1696. OnLineChanged(true, aLine, column, aEndChar - aStartChar, true);
  1697. line.erase(line.begin() + aStartChar, aEndChar == -1 ? line.end() : line.begin() + aEndChar);
  1698. OnLineChanged(false, aLine, column, aEndChar - aStartChar, true);
  1699. }
  1700. void TextEditor::AddGlyphsToLine(int aLine, int aTargetIndex, Line::iterator aSourceStart, Line::iterator aSourceEnd)
  1701. {
  1702. int targetColumn = GetCharacterColumn(aLine, aTargetIndex);
  1703. int charsInserted = std::distance(aSourceStart, aSourceEnd);
  1704. auto& line = mLines[aLine];
  1705. OnLineChanged(true, aLine, targetColumn, charsInserted, false);
  1706. line.insert(line.begin() + aTargetIndex, aSourceStart, aSourceEnd);
  1707. OnLineChanged(false, aLine, targetColumn, charsInserted, false);
  1708. }
  1709. void TextEditor::AddGlyphToLine(int aLine, int aTargetIndex, Glyph aGlyph)
  1710. {
  1711. int targetColumn = GetCharacterColumn(aLine, aTargetIndex);
  1712. auto& line = mLines[aLine];
  1713. OnLineChanged(true, aLine, targetColumn, 1, false);
  1714. line.insert(line.begin() + aTargetIndex, aGlyph);
  1715. OnLineChanged(false, aLine, targetColumn, 1, false);
  1716. }
  1717. ImU32 TextEditor::GetGlyphColor(const Glyph& aGlyph) const
  1718. {
  1719. if (mLanguageDefinition == nullptr)
  1720. return mPalette[(int)PaletteIndex::Default];
  1721. if (aGlyph.mComment)
  1722. return mPalette[(int)PaletteIndex::Comment];
  1723. if (aGlyph.mMultiLineComment)
  1724. return mPalette[(int)PaletteIndex::MultiLineComment];
  1725. auto const color = mPalette[(int)aGlyph.mColorIndex];
  1726. if (aGlyph.mPreprocessor)
  1727. {
  1728. const auto ppcolor = mPalette[(int)PaletteIndex::Preprocessor];
  1729. const int c0 = ((ppcolor & 0xff) + (color & 0xff)) / 2;
  1730. const int c1 = (((ppcolor >> 8) & 0xff) + ((color >> 8) & 0xff)) / 2;
  1731. const int c2 = (((ppcolor >> 16) & 0xff) + ((color >> 16) & 0xff)) / 2;
  1732. const int c3 = (((ppcolor >> 24) & 0xff) + ((color >> 24) & 0xff)) / 2;
  1733. return ImU32(c0 | (c1 << 8) | (c2 << 16) | (c3 << 24));
  1734. }
  1735. return color;
  1736. }
  1737. void TextEditor::HandleKeyboardInputs(bool aParentIsFocused)
  1738. {
  1739. if (ImGui::IsWindowFocused() || aParentIsFocused)
  1740. {
  1741. if (ImGui::IsWindowHovered())
  1742. ImGui::SetMouseCursor(ImGuiMouseCursor_TextInput);
  1743. //ImGui::CaptureKeyboardFromApp(true);
  1744. ImGuiIO& io = ImGui::GetIO();
  1745. auto isOSX = io.ConfigMacOSXBehaviors;
  1746. auto alt = io.KeyAlt;
  1747. auto ctrl = io.KeyCtrl;
  1748. auto shift = io.KeyShift;
  1749. auto super = io.KeySuper;
  1750. auto isShortcut = (isOSX ? (super && !ctrl) : (ctrl && !super)) && !alt && !shift;
  1751. auto isShiftShortcut = (isOSX ? (super && !ctrl) : (ctrl && !super)) && shift && !alt;
  1752. auto isWordmoveKey = isOSX ? alt : ctrl;
  1753. auto isAltOnly = alt && !ctrl && !shift && !super;
  1754. auto isCtrlOnly = ctrl && !alt && !shift && !super;
  1755. auto isShiftOnly = shift && !alt && !ctrl && !super;
  1756. io.WantCaptureKeyboard = true;
  1757. io.WantTextInput = true;
  1758. if (!mReadOnly && isShortcut && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Z)))
  1759. Undo();
  1760. else if (!mReadOnly && isAltOnly && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace)))
  1761. Undo();
  1762. else if (!mReadOnly && isShortcut && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Y)))
  1763. Redo();
  1764. else if (!mReadOnly && isShiftShortcut && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Z)))
  1765. Redo();
  1766. else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)))
  1767. MoveUp(1, shift);
  1768. else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow)))
  1769. MoveDown(1, shift);
  1770. else if ((isOSX ? !ctrl : !alt) && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow)))
  1771. MoveLeft(shift, isWordmoveKey);
  1772. else if ((isOSX ? !ctrl : !alt) && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow)))
  1773. MoveRight(shift, isWordmoveKey);
  1774. else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageUp)))
  1775. MoveUp(mVisibleLineCount - 2, shift);
  1776. else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageDown)))
  1777. MoveDown(mVisibleLineCount - 2, shift);
  1778. else if (ctrl && !alt && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home)))
  1779. MoveTop(shift);
  1780. else if (ctrl && !alt && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End)))
  1781. MoveBottom(shift);
  1782. else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home)))
  1783. MoveHome(shift);
  1784. else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End)))
  1785. MoveEnd(shift);
  1786. else if (!mReadOnly && !alt && !shift && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete)))
  1787. Delete(ctrl);
  1788. else if (!mReadOnly && !alt && !shift && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace)))
  1789. Backspace(ctrl);
  1790. else if (!mReadOnly && !alt && ctrl && shift && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_K)))
  1791. RemoveCurrentLines();
  1792. else if (!mReadOnly && !alt && ctrl && !shift && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftBracket)))
  1793. ChangeCurrentLinesIndentation(false);
  1794. else if (!mReadOnly && !alt && ctrl && !shift && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightBracket)))
  1795. ChangeCurrentLinesIndentation(true);
  1796. else if (!alt && ctrl && shift && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)))
  1797. MoveUpCurrentLines();
  1798. else if (!alt && ctrl && shift && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow)))
  1799. MoveDownCurrentLines();
  1800. else if (!mReadOnly && !alt && ctrl && !shift && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Slash)))
  1801. ToggleLineComment();
  1802. else if (!alt && !ctrl && !shift && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert)))
  1803. mOverwrite ^= true;
  1804. else if (isCtrlOnly && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert)))
  1805. Copy();
  1806. else if (isShortcut && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_C)))
  1807. Copy();
  1808. else if (!mReadOnly && isShiftOnly && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert)))
  1809. Paste();
  1810. else if (!mReadOnly && isShortcut && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_V)))
  1811. Paste();
  1812. else if (isShortcut && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_X)))
  1813. Cut();
  1814. else if (isShiftOnly && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete)))
  1815. Cut();
  1816. else if (isShortcut && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_A)))
  1817. SelectAll();
  1818. else if (isShortcut && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_D)))
  1819. AddCursorForNextOccurrence();
  1820. else if (!mReadOnly && !alt && !ctrl && !shift && !super && (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Enter)) || ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_KeypadEnter))))
  1821. EnterCharacter('\n', false);
  1822. else if (!mReadOnly && !alt && !ctrl && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Tab)))
  1823. EnterCharacter('\t', shift);
  1824. if (!mReadOnly && !io.InputQueueCharacters.empty() && ctrl == alt && !super)
  1825. {
  1826. for (int i = 0; i < io.InputQueueCharacters.Size; i++)
  1827. {
  1828. auto c = io.InputQueueCharacters[i];
  1829. if (c != 0 && (c == '\n' || c >= 32))
  1830. EnterCharacter(c, shift);
  1831. }
  1832. io.InputQueueCharacters.resize(0);
  1833. }
  1834. }
  1835. }
  1836. void TextEditor::HandleMouseInputs()
  1837. {
  1838. ImGuiIO& io = ImGui::GetIO();
  1839. auto shift = io.KeyShift;
  1840. auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl;
  1841. auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt;
  1842. /*
  1843. Pan with middle mouse button
  1844. */
  1845. mPanning &= ImGui::IsMouseDown(2);
  1846. if (mPanning && ImGui::IsMouseDragging(2))
  1847. {
  1848. ImVec2 scroll = { ImGui::GetScrollX(), ImGui::GetScrollY() };
  1849. ImVec2 currentMousePos = ImGui::GetMouseDragDelta(2);
  1850. ImVec2 mouseDelta = {
  1851. currentMousePos.x - mLastMousePos.x,
  1852. currentMousePos.y - mLastMousePos.y
  1853. };
  1854. ImGui::SetScrollY(scroll.y - mouseDelta.y);
  1855. ImGui::SetScrollX(scroll.x - mouseDelta.x);
  1856. mLastMousePos = currentMousePos;
  1857. }
  1858. // Mouse left button dragging (=> update selection)
  1859. mDraggingSelection &= ImGui::IsMouseDown(0);
  1860. if (mDraggingSelection && ImGui::IsMouseDragging(0))
  1861. {
  1862. io.WantCaptureMouse = true;
  1863. Coordinates cursorCoords = ScreenPosToCoordinates(ImGui::GetMousePos(), !mOverwrite);
  1864. SetCursorPosition(cursorCoords, mState.GetLastAddedCursorIndex(), false);
  1865. }
  1866. if (ImGui::IsWindowHovered())
  1867. {
  1868. auto click = ImGui::IsMouseClicked(0);
  1869. if (!shift && !alt)
  1870. {
  1871. auto doubleClick = ImGui::IsMouseDoubleClicked(0);
  1872. auto t = ImGui::GetTime();
  1873. auto tripleClick = click && !doubleClick &&
  1874. (mLastClickTime != -1.0f && (t - mLastClickTime) < io.MouseDoubleClickTime &&
  1875. Distance(io.MousePos, mLastClickPos) < 0.01f);
  1876. if (click)
  1877. mDraggingSelection = true;
  1878. /*
  1879. Pan with middle mouse button
  1880. */
  1881. if (ImGui::IsMouseClicked(2))
  1882. {
  1883. mPanning = true;
  1884. mLastMousePos = ImGui::GetMouseDragDelta(2);
  1885. }
  1886. /*
  1887. Left mouse button triple click
  1888. */
  1889. if (tripleClick)
  1890. {
  1891. if (ctrl)
  1892. mState.AddCursor();
  1893. else
  1894. mState.mCurrentCursor = 0;
  1895. Coordinates cursorCoords = ScreenPosToCoordinates(ImGui::GetMousePos());
  1896. Coordinates targetCursorPos = cursorCoords.mLine < mLines.size() - 1 ?
  1897. Coordinates{ cursorCoords.mLine + 1, 0 } :
  1898. Coordinates{ cursorCoords.mLine, GetLineMaxColumn(cursorCoords.mLine) };
  1899. SetSelection({ cursorCoords.mLine, 0 }, targetCursorPos, mState.mCurrentCursor);
  1900. mLastClickTime = -1.0f;
  1901. }
  1902. /*
  1903. Left mouse button double click
  1904. */
  1905. else if (doubleClick)
  1906. {
  1907. if (ctrl)
  1908. mState.AddCursor();
  1909. else
  1910. mState.mCurrentCursor = 0;
  1911. Coordinates cursorCoords = ScreenPosToCoordinates(ImGui::GetMousePos());
  1912. SetSelection(FindWordStart(cursorCoords), FindWordEnd(cursorCoords), mState.mCurrentCursor);
  1913. mLastClickTime = (float)ImGui::GetTime();
  1914. mLastClickPos = io.MousePos;
  1915. }
  1916. /*
  1917. Left mouse button click
  1918. */
  1919. else if (click)
  1920. {
  1921. if (ctrl)
  1922. mState.AddCursor();
  1923. else
  1924. mState.mCurrentCursor = 0;
  1925. bool isOverLineNumber;
  1926. Coordinates cursorCoords = ScreenPosToCoordinates(ImGui::GetMousePos(), !mOverwrite, &isOverLineNumber);
  1927. if (isOverLineNumber)
  1928. {
  1929. Coordinates targetCursorPos = cursorCoords.mLine < mLines.size() - 1 ?
  1930. Coordinates{ cursorCoords.mLine + 1, 0 } :
  1931. Coordinates{ cursorCoords.mLine, GetLineMaxColumn(cursorCoords.mLine) };
  1932. SetSelection({ cursorCoords.mLine, 0 }, targetCursorPos, mState.mCurrentCursor);
  1933. }
  1934. else
  1935. SetCursorPosition(cursorCoords, mState.GetLastAddedCursorIndex());
  1936. mLastClickTime = (float)ImGui::GetTime();
  1937. mLastClickPos = io.MousePos;
  1938. }
  1939. else if (ImGui::IsMouseReleased(0))
  1940. {
  1941. mState.SortCursorsFromTopToBottom();
  1942. MergeCursorsIfPossible();
  1943. }
  1944. }
  1945. else if (shift)
  1946. {
  1947. if (click)
  1948. {
  1949. Coordinates newSelection = ScreenPosToCoordinates(ImGui::GetMousePos(), !mOverwrite);
  1950. SetCursorPosition(newSelection, mState.mCurrentCursor, false);
  1951. }
  1952. }
  1953. }
  1954. }
  1955. void TextEditor::UpdateViewVariables(float aScrollX, float aScrollY)
  1956. {
  1957. mContentHeight = ImGui::GetWindowHeight() - (IsHorizontalScrollbarVisible() ? IMGUI_SCROLLBAR_WIDTH : 0.0f);
  1958. mContentWidth = ImGui::GetWindowWidth() - (IsVerticalScrollbarVisible() ? IMGUI_SCROLLBAR_WIDTH : 0.0f);
  1959. mVisibleLineCount = Max((int)ceil(mContentHeight / mCharAdvance.y), 0);
  1960. mFirstVisibleLine = Max((int)(aScrollY / mCharAdvance.y), 0);
  1961. mLastVisibleLine = Max((int)((mContentHeight + aScrollY) / mCharAdvance.y), 0);
  1962. mVisibleColumnCount = Max((int)ceil((mContentWidth - Max(mTextStart - aScrollX, 0.0f)) / mCharAdvance.x), 0);
  1963. mFirstVisibleColumn = Max((int)(Max(aScrollX - mTextStart, 0.0f) / mCharAdvance.x), 0);
  1964. mLastVisibleColumn = Max((int)((mContentWidth + aScrollX - mTextStart) / mCharAdvance.x), 0);
  1965. }
  1966. void TextEditor::Render(bool aParentIsFocused)
  1967. {
  1968. /* Compute mCharAdvance regarding to scaled font size (Ctrl + mouse wheel)*/
  1969. const float fontWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, "#", nullptr, nullptr).x;
  1970. const float fontHeight = ImGui::GetTextLineHeightWithSpacing();
  1971. mCharAdvance = ImVec2(fontWidth, fontHeight * mLineSpacing);
  1972. // Deduce mTextStart by evaluating mLines size (global lineMax) plus two spaces as text width
  1973. mTextStart = mLeftMargin;
  1974. static char lineNumberBuffer[16];
  1975. if (mShowLineNumbers)
  1976. {
  1977. snprintf(lineNumberBuffer, 16, " %d ", mLines.size());
  1978. mTextStart += ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, lineNumberBuffer, nullptr, nullptr).x;
  1979. }
  1980. ImVec2 cursorScreenPos = ImGui::GetCursorScreenPos();
  1981. mScrollX = ImGui::GetScrollX();
  1982. mScrollY = ImGui::GetScrollY();
  1983. UpdateViewVariables(mScrollX, mScrollY);
  1984. int maxColumnLimited = 0;
  1985. if (!mLines.empty())
  1986. {
  1987. auto drawList = ImGui::GetWindowDrawList();
  1988. float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr).x;
  1989. for (int lineNo = mFirstVisibleLine; lineNo <= mLastVisibleLine && lineNo < mLines.size(); lineNo++)
  1990. {
  1991. ImVec2 lineStartScreenPos = ImVec2(cursorScreenPos.x, cursorScreenPos.y + lineNo * mCharAdvance.y);
  1992. ImVec2 textScreenPos = ImVec2(lineStartScreenPos.x + mTextStart, lineStartScreenPos.y);
  1993. auto& line = mLines[lineNo];
  1994. maxColumnLimited = Max(GetLineMaxColumn(lineNo, mLastVisibleColumn), maxColumnLimited);
  1995. Coordinates lineStartCoord(lineNo, 0);
  1996. Coordinates lineEndCoord(lineNo, maxColumnLimited);
  1997. // Draw selection for the current line
  1998. for (int c = 0; c <= mState.mCurrentCursor; c++)
  1999. {
  2000. float rectStart = -1.0f;
  2001. float rectEnd = -1.0f;
  2002. Coordinates cursorSelectionStart = mState.mCursors[c].GetSelectionStart();
  2003. Coordinates cursorSelectionEnd = mState.mCursors[c].GetSelectionEnd();
  2004. assert(cursorSelectionStart <= cursorSelectionEnd);
  2005. if (cursorSelectionStart <= lineEndCoord)
  2006. rectStart = cursorSelectionStart > lineStartCoord ? TextDistanceToLineStart(cursorSelectionStart) : 0.0f;
  2007. if (cursorSelectionEnd > lineStartCoord)
  2008. rectEnd = TextDistanceToLineStart(cursorSelectionEnd < lineEndCoord ? cursorSelectionEnd : lineEndCoord);
  2009. if (cursorSelectionEnd.mLine > lineNo || cursorSelectionEnd.mLine == lineNo && cursorSelectionEnd > lineEndCoord)
  2010. rectEnd += mCharAdvance.x;
  2011. if (rectStart != -1 && rectEnd != -1 && rectStart < rectEnd)
  2012. drawList->AddRectFilled(
  2013. ImVec2{ lineStartScreenPos.x + mTextStart + rectStart, lineStartScreenPos.y },
  2014. ImVec2{ lineStartScreenPos.x + mTextStart + rectEnd, lineStartScreenPos.y + mCharAdvance.y },
  2015. mPalette[(int)PaletteIndex::Selection]);
  2016. }
  2017. // Draw line number (right aligned)
  2018. if (mShowLineNumbers)
  2019. {
  2020. snprintf(lineNumberBuffer, 16, "%d ", lineNo + 1);
  2021. float lineNoWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, lineNumberBuffer, nullptr, nullptr).x;
  2022. drawList->AddText(ImVec2(lineStartScreenPos.x + mTextStart - lineNoWidth, lineStartScreenPos.y), mPalette[(int)PaletteIndex::LineNumber], lineNumberBuffer);
  2023. }
  2024. std::vector<Coordinates> cursorCoordsInThisLine;
  2025. for (int c = 0; c <= mState.mCurrentCursor; c++)
  2026. {
  2027. if (mState.mCursors[c].mInteractiveEnd.mLine == lineNo)
  2028. cursorCoordsInThisLine.push_back(mState.mCursors[c].mInteractiveEnd);
  2029. }
  2030. if (cursorCoordsInThisLine.size() > 0)
  2031. {
  2032. bool focused = ImGui::IsWindowFocused() || aParentIsFocused;
  2033. // Render the cursors
  2034. if (focused)
  2035. {
  2036. for (const auto& cursorCoords : cursorCoordsInThisLine)
  2037. {
  2038. float width = 1.0f;
  2039. auto cindex = GetCharacterIndexR(cursorCoords);
  2040. float cx = TextDistanceToLineStart(cursorCoords);
  2041. if (mOverwrite && cindex < (int)line.size())
  2042. {
  2043. if (line[cindex].mChar == '\t')
  2044. {
  2045. auto x = (1.0f + std::floor((1.0f + cx) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize);
  2046. width = x - cx;
  2047. }
  2048. else
  2049. width = mCharAdvance.x;
  2050. }
  2051. ImVec2 cstart(textScreenPos.x + cx, lineStartScreenPos.y);
  2052. ImVec2 cend(textScreenPos.x + cx + width, lineStartScreenPos.y + mCharAdvance.y);
  2053. drawList->AddRectFilled(cstart, cend, mPalette[(int)PaletteIndex::Cursor]);
  2054. if (mCursorOnBracket)
  2055. {
  2056. ImVec2 topLeft = { cstart.x, lineStartScreenPos.y + fontHeight + 1.0f };
  2057. ImVec2 bottomRight = { topLeft.x + mCharAdvance.x, topLeft.y + 1.0f };
  2058. drawList->AddRectFilled(topLeft, bottomRight, mPalette[(int)PaletteIndex::Cursor]);
  2059. }
  2060. }
  2061. }
  2062. }
  2063. // Render colorized text
  2064. static std::string glyphBuffer;
  2065. int charIndex = GetFirstVisibleCharacterIndex(lineNo);
  2066. int column = mFirstVisibleColumn; // can be in the middle of tab character
  2067. while (charIndex < mLines[lineNo].size() && column <= mLastVisibleColumn)
  2068. {
  2069. auto& glyph = line[charIndex];
  2070. auto color = GetGlyphColor(glyph);
  2071. ImVec2 targetGlyphPos = { lineStartScreenPos.x + mTextStart + TextDistanceToLineStart({lineNo, column}, false), lineStartScreenPos.y };
  2072. if (glyph.mChar == '\t')
  2073. {
  2074. if (mShowWhitespaces)
  2075. {
  2076. ImVec2 p1, p2, p3, p4;
  2077. const auto s = ImGui::GetFontSize();
  2078. const auto x1 = targetGlyphPos.x + mCharAdvance.x * 0.3f;
  2079. const auto y = targetGlyphPos.y + fontHeight * 0.5f;
  2080. if (mShortTabs)
  2081. {
  2082. const auto x2 = targetGlyphPos.x + mCharAdvance.x;
  2083. p1 = ImVec2(x1, y);
  2084. p2 = ImVec2(x2, y);
  2085. p3 = ImVec2(x2 - s * 0.16f, y - s * 0.16f);
  2086. p4 = ImVec2(x2 - s * 0.16f, y + s * 0.16f);
  2087. }
  2088. else
  2089. {
  2090. const auto x2 = targetGlyphPos.x + TabSizeAtColumn(column) * mCharAdvance.x - mCharAdvance.x * 0.3f;
  2091. p1 = ImVec2(x1, y);
  2092. p2 = ImVec2(x2, y);
  2093. p3 = ImVec2(x2 - s * 0.2f, y - s * 0.2f);
  2094. p4 = ImVec2(x2 - s * 0.2f, y + s * 0.2f);
  2095. }
  2096. drawList->AddLine(p1, p2, mPalette[(int)PaletteIndex::ControlCharacter]);
  2097. drawList->AddLine(p2, p3, mPalette[(int)PaletteIndex::ControlCharacter]);
  2098. drawList->AddLine(p2, p4, mPalette[(int)PaletteIndex::ControlCharacter]);
  2099. }
  2100. }
  2101. else if (glyph.mChar == ' ')
  2102. {
  2103. if (mShowWhitespaces)
  2104. {
  2105. const auto s = ImGui::GetFontSize();
  2106. const auto x = targetGlyphPos.x + spaceSize * 0.5f;
  2107. const auto y = targetGlyphPos.y + s * 0.5f;
  2108. drawList->AddCircleFilled(ImVec2(x, y), 1.5f, mPalette[(int)PaletteIndex::ControlCharacter], 4);
  2109. }
  2110. }
  2111. else
  2112. {
  2113. int seqLength = UTF8CharLength(glyph.mChar);
  2114. if (mCursorOnBracket && seqLength == 1 && mMatchingBracketCoords == Coordinates{ lineNo, column })
  2115. {
  2116. ImVec2 topLeft = { targetGlyphPos.x, targetGlyphPos.y + fontHeight + 1.0f };
  2117. ImVec2 bottomRight = { topLeft.x + mCharAdvance.x, topLeft.y + 1.0f };
  2118. drawList->AddRectFilled(topLeft, bottomRight, mPalette[(int)PaletteIndex::Cursor]);
  2119. }
  2120. glyphBuffer.clear();
  2121. for (int i = 0; i < seqLength; i++)
  2122. glyphBuffer.push_back(line[charIndex + i].mChar);
  2123. drawList->AddText(targetGlyphPos, color, glyphBuffer.c_str());
  2124. }
  2125. MoveCharIndexAndColumn(lineNo, charIndex, column);
  2126. }
  2127. }
  2128. }
  2129. mCurrentSpaceHeight = (mLines.size() + Min(mVisibleLineCount - 1, (int)mLines.size())) * mCharAdvance.y;
  2130. mCurrentSpaceWidth = Max((maxColumnLimited + Min(mVisibleColumnCount - 1, maxColumnLimited)) * mCharAdvance.x, mCurrentSpaceWidth);
  2131. ImGui::SetCursorPos(ImVec2(0, 0));
  2132. ImGui::Dummy(ImVec2(mCurrentSpaceWidth, mCurrentSpaceHeight));
  2133. if (mEnsureCursorVisible > -1)
  2134. {
  2135. for (int i = 0; i < (mEnsureCursorVisibleStartToo ? 2 : 1); i++) // first pass for interactive end and second pass for interactive start
  2136. {
  2137. if (i) UpdateViewVariables(mScrollX, mScrollY); // second pass depends on changes made in first pass
  2138. Coordinates targetCoords = GetActualCursorCoordinates(mEnsureCursorVisible, i); // cursor selection end or start
  2139. if (targetCoords.mLine <= mFirstVisibleLine)
  2140. {
  2141. float targetScroll = std::max(0.0f, (targetCoords.mLine - 0.5f) * mCharAdvance.y);
  2142. if (targetScroll < mScrollY)
  2143. ImGui::SetScrollY(targetScroll);
  2144. }
  2145. if (targetCoords.mLine >= mLastVisibleLine)
  2146. {
  2147. float targetScroll = std::max(0.0f, (targetCoords.mLine + 1.5f) * mCharAdvance.y - mContentHeight);
  2148. if (targetScroll > mScrollY)
  2149. ImGui::SetScrollY(targetScroll);
  2150. }
  2151. if (targetCoords.mColumn <= mFirstVisibleColumn)
  2152. {
  2153. float targetScroll = std::max(0.0f, mTextStart + (targetCoords.mColumn - 0.5f) * mCharAdvance.x);
  2154. if (targetScroll < mScrollX)
  2155. ImGui::SetScrollX(mScrollX = targetScroll);
  2156. }
  2157. if (targetCoords.mColumn >= mLastVisibleColumn)
  2158. {
  2159. float targetScroll = std::max(0.0f, mTextStart + (targetCoords.mColumn + 0.5f) * mCharAdvance.x - mContentWidth);
  2160. if (targetScroll > mScrollX)
  2161. ImGui::SetScrollX(mScrollX = targetScroll);
  2162. }
  2163. }
  2164. mEnsureCursorVisible = -1;
  2165. }
  2166. if (mScrollToTop)
  2167. {
  2168. ImGui::SetScrollY(0.0f);
  2169. mScrollToTop = false;
  2170. }
  2171. if (mSetViewAtLine > -1)
  2172. {
  2173. float targetScroll;
  2174. switch (mSetViewAtLineMode)
  2175. {
  2176. default:
  2177. case SetViewAtLineMode::FirstVisibleLine:
  2178. targetScroll = std::max(0.0f, (float)mSetViewAtLine * mCharAdvance.y);
  2179. break;
  2180. case SetViewAtLineMode::LastVisibleLine:
  2181. targetScroll = std::max(0.0f, (float)(mSetViewAtLine - (mLastVisibleLine - mFirstVisibleLine)) * mCharAdvance.y);
  2182. break;
  2183. case SetViewAtLineMode::Centered:
  2184. targetScroll = std::max(0.0f, ((float)mSetViewAtLine - (float)(mLastVisibleLine - mFirstVisibleLine) * 0.5f) * mCharAdvance.y);
  2185. break;
  2186. }
  2187. ImGui::SetScrollY(targetScroll);
  2188. mSetViewAtLine = -1;
  2189. }
  2190. }
  2191. void TextEditor::OnCursorPositionChanged()
  2192. {
  2193. if (mState.mCurrentCursor == 0 && !mState.mCursors[0].HasSelection()) // only one cursor without selection
  2194. mCursorOnBracket = FindMatchingBracket(mState.mCursors[0].mInteractiveEnd.mLine,
  2195. GetCharacterIndexR(mState.mCursors[0].mInteractiveEnd), mMatchingBracketCoords);
  2196. else
  2197. mCursorOnBracket = false;
  2198. if (!mDraggingSelection)
  2199. {
  2200. mState.SortCursorsFromTopToBottom();
  2201. MergeCursorsIfPossible();
  2202. }
  2203. }
  2204. void TextEditor::OnLineChanged(bool aBeforeChange, int aLine, int aColumn, int aCharCount, bool aDeleted) // adjusts cursor position when other cursor writes/deletes in the same line
  2205. {
  2206. static std::unordered_map<int, int> cursorCharIndices;
  2207. if (aBeforeChange)
  2208. {
  2209. cursorCharIndices.clear();
  2210. for (int c = 0; c <= mState.mCurrentCursor; c++)
  2211. {
  2212. if (mState.mCursors[c].mInteractiveEnd.mLine == aLine && // cursor is at the line
  2213. mState.mCursors[c].mInteractiveEnd.mColumn > aColumn && // cursor is to the right of changing part
  2214. mState.mCursors[c].GetSelectionEnd() == mState.mCursors[c].GetSelectionStart()) // cursor does not have a selection
  2215. {
  2216. cursorCharIndices[c] = GetCharacterIndexR({ aLine, mState.mCursors[c].mInteractiveEnd.mColumn });
  2217. cursorCharIndices[c] += aDeleted ? -aCharCount : aCharCount;
  2218. }
  2219. }
  2220. }
  2221. else
  2222. {
  2223. for (auto& item : cursorCharIndices)
  2224. SetCursorPosition({ aLine, GetCharacterColumn(aLine, item.second) }, item.first);
  2225. }
  2226. }
  2227. void TextEditor::MergeCursorsIfPossible()
  2228. {
  2229. // requires the cursors to be sorted from top to bottom
  2230. std::unordered_set<int> cursorsToDelete;
  2231. if (AnyCursorHasSelection())
  2232. {
  2233. // merge cursors if they overlap
  2234. for (int c = mState.mCurrentCursor; c > 0; c--)// iterate backwards through pairs
  2235. {
  2236. int pc = c - 1; // pc for previous cursor
  2237. bool pcContainsC = mState.mCursors[pc].GetSelectionEnd() >= mState.mCursors[c].GetSelectionEnd();
  2238. bool pcContainsStartOfC = mState.mCursors[pc].GetSelectionEnd() > mState.mCursors[c].GetSelectionStart();
  2239. if (pcContainsC)
  2240. {
  2241. cursorsToDelete.insert(c);
  2242. }
  2243. else if (pcContainsStartOfC)
  2244. {
  2245. Coordinates pcStart = mState.mCursors[pc].GetSelectionStart();
  2246. Coordinates cEnd = mState.mCursors[c].GetSelectionEnd();
  2247. mState.mCursors[pc].mInteractiveEnd = cEnd;
  2248. mState.mCursors[pc].mInteractiveStart = pcStart;
  2249. cursorsToDelete.insert(c);
  2250. }
  2251. }
  2252. }
  2253. else
  2254. {
  2255. // merge cursors if they are at the same position
  2256. for (int c = mState.mCurrentCursor; c > 0; c--)// iterate backwards through pairs
  2257. {
  2258. int pc = c - 1;
  2259. if (mState.mCursors[pc].mInteractiveEnd == mState.mCursors[c].mInteractiveEnd)
  2260. cursorsToDelete.insert(c);
  2261. }
  2262. }
  2263. for (int c = mState.mCurrentCursor; c > -1; c--)// iterate backwards through each of them
  2264. {
  2265. if (cursorsToDelete.find(c) != cursorsToDelete.end())
  2266. mState.mCursors.erase(mState.mCursors.begin() + c);
  2267. }
  2268. mState.mCurrentCursor -= cursorsToDelete.size();
  2269. }
  2270. void TextEditor::AddUndo(UndoRecord& aValue)
  2271. {
  2272. assert(!mReadOnly);
  2273. mUndoBuffer.resize((size_t)(mUndoIndex + 1));
  2274. mUndoBuffer.back() = aValue;
  2275. ++mUndoIndex;
  2276. mTextChanged = true;
  2277. }
  2278. // TODO
  2279. // - multiline comments vs single-line: latter is blocking start of a ML
  2280. void TextEditor::Colorize(int aFromLine, int aLines)
  2281. {
  2282. int toLine = aLines == -1 ? (int)mLines.size() : std::min((int)mLines.size(), aFromLine + aLines);
  2283. mColorRangeMin = std::min(mColorRangeMin, aFromLine);
  2284. mColorRangeMax = std::max(mColorRangeMax, toLine);
  2285. mColorRangeMin = std::max(0, mColorRangeMin);
  2286. mColorRangeMax = std::max(mColorRangeMin, mColorRangeMax);
  2287. mCheckComments = true;
  2288. }
  2289. void TextEditor::ColorizeRange(int aFromLine, int aToLine)
  2290. {
  2291. if (mLines.empty() || aFromLine >= aToLine || mLanguageDefinition == nullptr)
  2292. return;
  2293. std::string buffer;
  2294. boost::cmatch results;
  2295. std::string id;
  2296. int endLine = std::max(0, std::min((int)mLines.size(), aToLine));
  2297. for (int i = aFromLine; i < endLine; ++i)
  2298. {
  2299. auto& line = mLines[i];
  2300. if (line.empty())
  2301. continue;
  2302. buffer.resize(line.size());
  2303. for (size_t j = 0; j < line.size(); ++j)
  2304. {
  2305. auto& col = line[j];
  2306. buffer[j] = col.mChar;
  2307. col.mColorIndex = PaletteIndex::Default;
  2308. }
  2309. const char* bufferBegin = &buffer.front();
  2310. const char* bufferEnd = bufferBegin + buffer.size();
  2311. auto last = bufferEnd;
  2312. for (auto first = bufferBegin; first != last; )
  2313. {
  2314. const char* token_begin = nullptr;
  2315. const char* token_end = nullptr;
  2316. PaletteIndex token_color = PaletteIndex::Default;
  2317. bool hasTokenizeResult = false;
  2318. if (mLanguageDefinition->mTokenize != nullptr)
  2319. {
  2320. if (mLanguageDefinition->mTokenize(first, last, token_begin, token_end, token_color))
  2321. hasTokenizeResult = true;
  2322. }
  2323. if (hasTokenizeResult == false)
  2324. {
  2325. // todo : remove
  2326. //printf("using regex for %.*s\n", first + 10 < last ? 10 : int(last - first), first);
  2327. for (const auto& p : mRegexList)
  2328. {
  2329. bool regexSearchResult = false;
  2330. try { regexSearchResult = boost::regex_search(first, last, results, p.first, boost::regex_constants::match_continuous); }
  2331. catch (...) {}
  2332. if (regexSearchResult)
  2333. {
  2334. hasTokenizeResult = true;
  2335. auto& v = *results.begin();
  2336. token_begin = v.first;
  2337. token_end = v.second;
  2338. token_color = p.second;
  2339. break;
  2340. }
  2341. }
  2342. }
  2343. if (hasTokenizeResult == false)
  2344. {
  2345. first++;
  2346. }
  2347. else
  2348. {
  2349. const size_t token_length = token_end - token_begin;
  2350. if (token_color == PaletteIndex::Identifier)
  2351. {
  2352. id.assign(token_begin, token_end);
  2353. // todo : allmost all language definitions use lower case to specify keywords, so shouldn't this use ::tolower ?
  2354. if (!mLanguageDefinition->mCaseSensitive)
  2355. std::transform(id.begin(), id.end(), id.begin(), ::toupper);
  2356. if (!line[first - bufferBegin].mPreprocessor)
  2357. {
  2358. if (mLanguageDefinition->mKeywords.count(id) != 0)
  2359. token_color = PaletteIndex::Keyword;
  2360. else if (mLanguageDefinition->mIdentifiers.count(id) != 0)
  2361. token_color = PaletteIndex::KnownIdentifier;
  2362. else if (mLanguageDefinition->mPreprocIdentifiers.count(id) != 0)
  2363. token_color = PaletteIndex::PreprocIdentifier;
  2364. }
  2365. else
  2366. {
  2367. if (mLanguageDefinition->mPreprocIdentifiers.count(id) != 0)
  2368. token_color = PaletteIndex::PreprocIdentifier;
  2369. }
  2370. }
  2371. for (size_t j = 0; j < token_length; ++j)
  2372. line[(token_begin - bufferBegin) + j].mColorIndex = token_color;
  2373. first = token_end;
  2374. }
  2375. }
  2376. }
  2377. }
  2378. template<class InputIt1, class InputIt2, class BinaryPredicate>
  2379. bool ColorizerEquals(InputIt1 first1, InputIt1 last1,
  2380. InputIt2 first2, InputIt2 last2, BinaryPredicate p)
  2381. {
  2382. for (; first1 != last1 && first2 != last2; ++first1, ++first2)
  2383. {
  2384. if (!p(*first1, *first2))
  2385. return false;
  2386. }
  2387. return first1 == last1 && first2 == last2;
  2388. }
  2389. void TextEditor::ColorizeInternal()
  2390. {
  2391. if (mLines.empty() || mLanguageDefinition == nullptr)
  2392. return;
  2393. if (mCheckComments)
  2394. {
  2395. auto endLine = mLines.size();
  2396. auto endIndex = 0;
  2397. auto commentStartLine = endLine;
  2398. auto commentStartIndex = endIndex;
  2399. auto withinString = false;
  2400. auto withinSingleLineComment = false;
  2401. auto withinPreproc = false;
  2402. auto firstChar = true; // there is no other non-whitespace characters in the line before
  2403. auto concatenate = false; // '\' on the very end of the line
  2404. auto currentLine = 0;
  2405. auto currentIndex = 0;
  2406. while (currentLine < endLine || currentIndex < endIndex)
  2407. {
  2408. auto& line = mLines[currentLine];
  2409. if (currentIndex == 0 && !concatenate)
  2410. {
  2411. withinSingleLineComment = false;
  2412. withinPreproc = false;
  2413. firstChar = true;
  2414. }
  2415. concatenate = false;
  2416. if (!line.empty())
  2417. {
  2418. auto& g = line[currentIndex];
  2419. auto c = g.mChar;
  2420. if (c != mLanguageDefinition->mPreprocChar && !isspace(c))
  2421. firstChar = false;
  2422. if (currentIndex == (int)line.size() - 1 && line[line.size() - 1].mChar == '\\')
  2423. concatenate = true;
  2424. bool inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex));
  2425. if (withinString)
  2426. {
  2427. line[currentIndex].mMultiLineComment = inComment;
  2428. if (c == '\"')
  2429. {
  2430. if (currentIndex + 1 < (int)line.size() && line[currentIndex + 1].mChar == '\"')
  2431. {
  2432. currentIndex += 1;
  2433. if (currentIndex < (int)line.size())
  2434. line[currentIndex].mMultiLineComment = inComment;
  2435. }
  2436. else
  2437. withinString = false;
  2438. }
  2439. else if (c == '\\')
  2440. {
  2441. currentIndex += 1;
  2442. if (currentIndex < (int)line.size())
  2443. line[currentIndex].mMultiLineComment = inComment;
  2444. }
  2445. }
  2446. else
  2447. {
  2448. if (firstChar && c == mLanguageDefinition->mPreprocChar)
  2449. withinPreproc = true;
  2450. if (c == '\"')
  2451. {
  2452. withinString = true;
  2453. line[currentIndex].mMultiLineComment = inComment;
  2454. }
  2455. else
  2456. {
  2457. auto pred = [](const char& a, const Glyph& b) { return a == b.mChar; };
  2458. auto from = line.begin() + currentIndex;
  2459. auto& startStr = mLanguageDefinition->mCommentStart;
  2460. auto& singleStartStr = mLanguageDefinition->mSingleLineComment;
  2461. if (!withinSingleLineComment && currentIndex + startStr.size() <= line.size() &&
  2462. ColorizerEquals(startStr.begin(), startStr.end(), from, from + startStr.size(), pred))
  2463. {
  2464. commentStartLine = currentLine;
  2465. commentStartIndex = currentIndex;
  2466. }
  2467. else if (singleStartStr.size() > 0 &&
  2468. currentIndex + singleStartStr.size() <= line.size() &&
  2469. ColorizerEquals(singleStartStr.begin(), singleStartStr.end(), from, from + singleStartStr.size(), pred))
  2470. {
  2471. withinSingleLineComment = true;
  2472. }
  2473. inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex));
  2474. line[currentIndex].mMultiLineComment = inComment;
  2475. line[currentIndex].mComment = withinSingleLineComment;
  2476. auto& endStr = mLanguageDefinition->mCommentEnd;
  2477. if (currentIndex + 1 >= (int)endStr.size() &&
  2478. ColorizerEquals(endStr.begin(), endStr.end(), from + 1 - endStr.size(), from + 1, pred))
  2479. {
  2480. commentStartIndex = endIndex;
  2481. commentStartLine = endLine;
  2482. }
  2483. }
  2484. }
  2485. if (currentIndex < (int)line.size())
  2486. line[currentIndex].mPreprocessor = withinPreproc;
  2487. currentIndex += UTF8CharLength(c);
  2488. if (currentIndex >= (int)line.size())
  2489. {
  2490. currentIndex = 0;
  2491. ++currentLine;
  2492. }
  2493. }
  2494. else
  2495. {
  2496. currentIndex = 0;
  2497. ++currentLine;
  2498. }
  2499. }
  2500. mCheckComments = false;
  2501. }
  2502. if (mColorRangeMin < mColorRangeMax)
  2503. {
  2504. const int increment = (mLanguageDefinition->mTokenize == nullptr) ? 10 : 10000;
  2505. const int to = std::min(mColorRangeMin + increment, mColorRangeMax);
  2506. ColorizeRange(mColorRangeMin, to);
  2507. mColorRangeMin = to;
  2508. if (mColorRangeMax == mColorRangeMin)
  2509. {
  2510. mColorRangeMin = std::numeric_limits<int>::max();
  2511. mColorRangeMax = 0;
  2512. }
  2513. return;
  2514. }
  2515. }
  2516. const TextEditor::Palette& TextEditor::GetDarkPalette()
  2517. {
  2518. const static Palette p = { {
  2519. 0xdcdfe4ff, // Default
  2520. 0xe06c75ff, // Keyword
  2521. 0xe5c07bff, // Number
  2522. 0x98c379ff, // String
  2523. 0xe0a070ff, // Char literal
  2524. 0x6a7384ff, // Punctuation
  2525. 0x808040ff, // Preprocessor
  2526. 0xdcdfe4ff, // Identifier
  2527. 0x61afefff, // Known identifier
  2528. 0xc678ddff, // Preproc identifier
  2529. 0x3696a2ff, // Comment (single line)
  2530. 0x3696a2ff, // Comment (multi line)
  2531. 0x282c3400, // Background transparent
  2532. //0x282c34ff, // Background
  2533. 0xe0e0e0ff, // Cursor
  2534. 0x2060a080, // Selection
  2535. 0xff200080, // ErrorMarker
  2536. 0xffffff15, // ControlCharacter
  2537. 0x0080f040, // Breakpoint
  2538. 0x7a8394ff, // Line number
  2539. 0x00000040, // Current line fill
  2540. 0x80808040, // Current line fill (inactive)
  2541. 0xa0a0a040, // Current line edge
  2542. } };
  2543. return p;
  2544. }
  2545. const TextEditor::Palette& TextEditor::GetMarianaPalette()
  2546. {
  2547. const static Palette p = { {
  2548. 0xffffffff, // Default
  2549. 0xc695c6ff, // Keyword
  2550. 0xf9ae58ff, // Number
  2551. 0x99c794ff, // String
  2552. 0xe0a070ff, // Char literal
  2553. 0x5fb4b4ff, // Punctuation
  2554. 0x808040ff, // Preprocessor
  2555. 0xffffffff, // Identifier
  2556. 0x4dc69bff, // Known identifier
  2557. 0xe0a0ffff, // Preproc identifier
  2558. 0xa6acb9ff, // Comment (single line)
  2559. 0xa6acb9ff, // Comment (multi line)
  2560. 0x303841ff, // Background
  2561. 0xe0e0e0ff, // Cursor
  2562. 0x6e7a8580, // Selection
  2563. 0xec5f6680, // ErrorMarker
  2564. 0xffffff30, // ControlCharacter
  2565. 0x0080f040, // Breakpoint
  2566. 0xffffffb0, // Line number
  2567. 0x4e5a6580, // Current line fill
  2568. 0x4e5a6530, // Current line fill (inactive)
  2569. 0x4e5a65b0, // Current line edge
  2570. } };
  2571. return p;
  2572. }
  2573. const TextEditor::Palette& TextEditor::GetLightPalette()
  2574. {
  2575. const static Palette p = { {
  2576. 0x404040ff, // None
  2577. 0x060cffff, // Keyword
  2578. 0x008000ff, // Number
  2579. 0xa02020ff, // String
  2580. 0x704030ff, // Char literal
  2581. 0x000000ff, // Punctuation
  2582. 0x606040ff, // Preprocessor
  2583. 0x404040ff, // Identifier
  2584. 0x106060ff, // Known identifier
  2585. 0xa040c0ff, // Preproc identifier
  2586. 0x205020ff, // Comment (single line)
  2587. 0x205040ff, // Comment (multi line)
  2588. 0xffffffff, // Background
  2589. 0x000000ff, // Cursor
  2590. 0x00006040, // Selection
  2591. 0xff1000a0, // ErrorMarker
  2592. 0x90909090, // ControlCharacter
  2593. 0x0080f080, // Breakpoint
  2594. 0x005050ff, // Line number
  2595. 0x00000040, // Current line fill
  2596. 0x80808040, // Current line fill (inactive)
  2597. 0x00000040, // Current line edge
  2598. } };
  2599. return p;
  2600. }
  2601. const TextEditor::Palette& TextEditor::GetRetroBluePalette()
  2602. {
  2603. const static Palette p = { {
  2604. 0xffff00ff, // None
  2605. 0x00ffffff, // Keyword
  2606. 0x00ff00ff, // Number
  2607. 0x008080ff, // String
  2608. 0x008080ff, // Char literal
  2609. 0xffffffff, // Punctuation
  2610. 0x008000ff, // Preprocessor
  2611. 0xffff00ff, // Identifier
  2612. 0xffffffff, // Known identifier
  2613. 0xff00ffff, // Preproc identifier
  2614. 0x808080ff, // Comment (single line)
  2615. 0x404040ff, // Comment (multi line)
  2616. 0x000080ff, // Background
  2617. 0xff8000ff, // Cursor
  2618. 0x00ffff80, // Selection
  2619. 0xff0000a0, // ErrorMarker
  2620. 0x0080ff80, // Breakpoint
  2621. 0x008080ff, // Line number
  2622. 0x00000040, // Current line fill
  2623. 0x80808040, // Current line fill (inactive)
  2624. 0x00000040, // Current line edge
  2625. } };
  2626. return p;
  2627. }
  2628. const std::unordered_map<char, char> TextEditor::OPEN_TO_CLOSE_CHAR = {
  2629. {'{', '}'},
  2630. {'(' , ')'},
  2631. {'[' , ']'}
  2632. };
  2633. const std::unordered_map<char, char> TextEditor::CLOSE_TO_OPEN_CHAR = {
  2634. {'}', '{'},
  2635. {')' , '('},
  2636. {']' , '['}
  2637. };
  2638. TextEditor::PaletteId TextEditor::defaultPalette = TextEditor::PaletteId::Dark;