11#include " CustomTextEditor.h"
22#include " SemanticSuggestions.h"
3+ #include " engine_api_suggestions.h"
34#include " external/IconsFontAwesome6.h"
45#include " util/UIUtils.h"
56#include " App.h"
1011#include < numeric>
1112#include < sstream>
1213#include < cstring>
14+ #include < regex>
1315
1416#ifdef _WIN32
1517#include < Windows.h>
@@ -118,10 +120,10 @@ void CustomTextEditor::initializeLanguage() {
118120 " string" , " vector" , " map" , " unordered_map" , " set" , " unordered_set" ,
119121 " array" , " list" , " deque" , " queue" , " stack" , " pair" , " tuple" ,
120122 " unique_ptr" , " shared_ptr" , " weak_ptr" ,
121- " function" , " optional" , " variant" , " any" ,
122- " Entity" , " Scene" , " Transform" , " Vector2" , " Vector3" , " Vector4" ,
123- " Matrix4" , " Quaternion" , " Color" , " Rect"
123+ " function" , " optional" , " variant" , " any"
124124 };
125+ // Add engine types from auto-generated header
126+ for (const auto & t : getEngineTypeNames ()) languageDef.types .insert (t);
125127 languageDef.singleLineComment = " //" ;
126128 languageDef.multiLineCommentStart = " /*" ;
127129 languageDef.multiLineCommentEnd = " */" ;
@@ -138,13 +140,18 @@ void CustomTextEditor::initializeLanguage() {
138140 " string" , " number" , " boolean" , " table" , " function" , " thread" ,
139141 " userdata"
140142 };
143+ // Add engine types from auto-generated header
144+ for (const auto & t : getEngineTypeNames ()) languageDef.types .insert (t);
141145 languageDef.builtinFunctions = {
142146 " assert" , " collectgarbage" , " dofile" , " error" , " getmetatable" ,
143147 " ipairs" , " load" , " loadfile" , " next" , " pairs" , " pcall" , " print" ,
144148 " rawequal" , " rawget" , " rawlen" , " rawset" , " require" , " select" ,
145149 " setmetatable" , " tonumber" , " tostring" , " type" , " xpcall" ,
146150 " coroutine" , " debug" , " io" , " math" , " os" , " package" , " string" , " table" , " utf8"
147151 };
152+ // Add engine builtins (static classes + enums) from auto-generated header
153+ for (const auto & b : getEngineBuiltinNames ()) languageDef.builtinFunctions .insert (b);
154+ for (const auto & e : getEngineEnumNames ()) languageDef.builtinFunctions .insert (e);
148155 languageDef.singleLineComment = " --" ;
149156 languageDef.multiLineCommentStart = " --[[" ;
150157 languageDef.multiLineCommentEnd = " ]]" ;
@@ -277,6 +284,48 @@ void CustomTextEditor::initializeSuggestions() {
277284 suggestions->AddSnippet (" cmake_min" , " cmake_minimum_required(VERSION ${1:3.16})" , " cmake minimum version" );
278285 suggestions->AddSnippet (" project" , " project(${1:name}\n\t VERSION ${2:1.0.0}\n\t LANGUAGES ${3:CXX}\n )" , " project definition" );
279286 }
287+
288+ // Add engine API suggestions for Lua and C++
289+ if (language == SyntaxLanguage::Lua || language == SyntaxLanguage::Cpp) {
290+ addEngineAPISuggestions ();
291+ }
292+ }
293+
294+ void CustomTextEditor::addEngineAPISuggestions () {
295+ if (!suggestions) return ;
296+
297+ static const auto & apiSymbols = getEngineAPISymbols ();
298+
299+ static const std::unordered_map<std::string, SuggestionKind> kindMap = {
300+ {" Class" , SuggestionKind::Class},
301+ {" Enum" , SuggestionKind::Enum},
302+ {" EnumMember" , SuggestionKind::EnumMember},
303+ {" Method" , SuggestionKind::Method},
304+ {" Function" , SuggestionKind::Function},
305+ {" Property" , SuggestionKind::Property},
306+ {" Constant" , SuggestionKind::Constant},
307+ {" Variable" , SuggestionKind::Variable},
308+ };
309+
310+ for (const auto & sym : apiSymbols) {
311+ SuggestionKind sk = SuggestionKind::Variable;
312+ auto it = kindMap.find (sym.kind );
313+ if (it != kindMap.end ()) sk = it->second ;
314+ suggestions->AddSymbol (sym.name , sk, sym.detail , sym.parent ? sym.parent : " " );
315+
316+ // Build inheritance map from class detail strings like "class Mesh : Object"
317+ if (sk == SuggestionKind::Class && sym.detail ) {
318+ std::string detail = sym.detail ;
319+ size_t colonPos = detail.find (" : " );
320+ if (colonPos != std::string::npos) {
321+ std::string className = sym.name ;
322+ std::string parentName = detail.substr (colonPos + 3 );
323+ // Trim any trailing whitespace
324+ while (!parentName.empty () && parentName.back () == ' ' ) parentName.pop_back ();
325+ suggestions->SetClassParent (className, parentName);
326+ }
327+ }
328+ }
280329}
281330
282331void CustomTextEditor::SetLanguage (SyntaxLanguage lang) {
@@ -1756,11 +1805,61 @@ char CustomTextEditor::findMatchingBracket(const TextPosition& pos, TextPosition
17561805 return target;
17571806}
17581807
1808+ std::string CustomTextEditor::inferTypeOfVariable (const std::string& varName, int currentLine) const {
1809+ if (varName.empty ()) return " " ;
1810+
1811+ // C++ patterns:
1812+ // 1. Standard declaration: [const] Type [*&] varName
1813+ std::regex cppDeclRegex (" (?:const\\ s+)?([A-Za-z0-9_:]+)(?:<[^>]*>)?\\ s*(?:[*&]+\\ s*)?" + varName + " \\ b" );
1814+ // 2. auto varName = new Type(...)
1815+ std::regex cppNewRegex (" \\ bauto\\ s*[*&]*\\ s*" + varName + " \\ s*=\\ s*new\\ s+([A-Za-z0-9_:]+)" );
1816+ // 3. auto varName = Type(...) or auto varName = Type{...}
1817+ std::regex cppAutoCtorRegex (" \\ bauto\\ s*[*&]*\\ s*" + varName + " \\ s*=\\ s*([A-Z][A-Za-z0-9_:]*)\\ s*[({]" );
1818+
1819+ std::regex luaVarRegex (" \\ b" + varName + " \\ s*=\\ s*([A-Za-z0-9_\\ .:]+)(?:\\ (|\\ {)" );
1820+ std::regex luaLocalVarRegex (" \\ blocal\\ s+" + varName + " \\ s*=\\ s*([A-Za-z0-9_\\ .:]+)(?:\\ (|\\ {)" );
1821+
1822+ // Search backwards from the current line
1823+ for (int i = currentLine; i >= 0 ; --i) {
1824+ if (i >= static_cast <int >(lines.size ())) continue ;
1825+ const std::string& line = lines[i];
1826+ std::smatch match;
1827+
1828+ if (language == SyntaxLanguage::Cpp) {
1829+ // Try auto = new Type first (most specific)
1830+ if (std::regex_search (line, match, cppNewRegex)) {
1831+ return match[1 ].str ();
1832+ }
1833+ // Try auto = Type() / Type{}
1834+ if (std::regex_search (line, match, cppAutoCtorRegex)) {
1835+ return match[1 ].str ();
1836+ }
1837+ // Try standard declaration
1838+ if (std::regex_search (line, match, cppDeclRegex)) {
1839+ std::string typeMatch = match[1 ].str ();
1840+ if (typeMatch != " return" && typeMatch != " new" && typeMatch != " delete" && typeMatch != " auto" ) {
1841+ return typeMatch;
1842+ }
1843+ }
1844+ } else if (language == SyntaxLanguage::Lua) {
1845+ if (std::regex_search (line, match, luaLocalVarRegex)) {
1846+ return match[1 ].str ();
1847+ }
1848+ if (std::regex_search (line, match, luaVarRegex)) {
1849+ return match[1 ].str ();
1850+ }
1851+ }
1852+ }
1853+ return " " ;
1854+ }
1855+
17591856SuggestionContext CustomTextEditor::buildSuggestionContext () const {
17601857 SuggestionContext ctx;
17611858
17621859 if (cursors.empty ()) return ctx;
17631860
1861+ ctx.isCpp = (language == SyntaxLanguage::Cpp);
1862+
17641863 const TextPosition& pos = cursors[primaryCursor].position ;
17651864
17661865 // Get current word being typed
@@ -1784,22 +1883,52 @@ SuggestionContext CustomTextEditor::buildSuggestionContext() const {
17841883 ctx.afterDot = true ;
17851884 } else if (c == ' >' && checkCol > 0 && ctx.lineContent [checkCol - 1 ] == ' -' ) {
17861885 ctx.afterArrow = true ;
1886+ checkCol--; // move past '-'
17871887 } else if (c == ' :' && checkCol > 0 && ctx.lineContent [checkCol - 1 ] == ' :' ) {
17881888 ctx.afterDoubleColon = true ;
1889+ checkCol--; // move past first ':'
1890+ } else if (c == ' :' ) {
1891+ if (language == SyntaxLanguage::Lua) {
1892+ ctx.afterColon = true ;
1893+ }
17891894 }
17901895 }
17911896 }
17921897
1793- // Get previous word for context
1794- if (wordStart. column > 1 ) {
1795- TextPosition prevEnd (pos.line , wordStart. column - 1 );
1796- // Skip whitespace
1797- while (prevEnd.column > 0 && std::isspace (ctx.lineContent [prevEnd.column - 1 ])) {
1898+ // Get previous word for context (e.g. before the dot)
1899+ if (checkCol > 0 ) {
1900+ TextPosition prevEnd (pos.line , checkCol - 1 );
1901+ // Skip whitespace if any
1902+ while (prevEnd.column > 0 && std::isspace (ctx.lineContent [prevEnd.column ])) {
17981903 prevEnd.column --;
17991904 }
1800- if (prevEnd.column > 0 ) {
1801- TextPosition prevStart = findWordStart (prevEnd);
1802- ctx.previousWord = getRange (prevStart, prevEnd);
1905+ if (prevEnd.column >= 0 && (std::isalnum (ctx.lineContent [prevEnd.column ]) || ctx.lineContent [prevEnd.column ] == ' _' )) {
1906+ TextPosition prevStart = findWordStart (TextPosition (pos.line , prevEnd.column + 1 ));
1907+ // getRange uses end column generically as exclusive bound, so we use prevEnd.column+1
1908+ ctx.previousWord = getRange (prevStart, TextPosition (pos.line , prevEnd.column + 1 ));
1909+ }
1910+ }
1911+
1912+ // Infer the type of the previous word for semantic suggestions
1913+ if (ctx.afterDot || ctx.afterArrow || ctx.afterDoubleColon || ctx.afterColon ) {
1914+ if (!ctx.previousWord .empty ()) {
1915+ if (ctx.afterDoubleColon ) {
1916+ // For ::, the previousWord IS the type name (e.g. Vector3::ZERO)
1917+ ctx.targetType = ctx.previousWord ;
1918+ } else {
1919+ ctx.targetType = inferTypeOfVariable (ctx.previousWord , pos.line );
1920+ // Fallback: look up the variable in project symbols (e.g. header member variables)
1921+ if (ctx.targetType .empty () && suggestions) {
1922+ ctx.targetType = suggestions->FindSymbolType (ctx.previousWord );
1923+ }
1924+ }
1925+ // Strip namespace prefix for matching engine API parent types (e.g. "doriax::Mesh" -> "Mesh")
1926+ if (!ctx.targetType .empty ()) {
1927+ size_t lastColon = ctx.targetType .rfind (' :' );
1928+ if (lastColon != std::string::npos) {
1929+ ctx.targetType = ctx.targetType .substr (lastColon + 1 );
1930+ }
1931+ }
18031932 }
18041933 }
18051934
@@ -1821,6 +1950,23 @@ void CustomTextEditor::CloseAutoComplete() {
18211950 autoCompleteItems.clear ();
18221951}
18231952
1953+ void CustomTextEditor::UpdateProjectSymbols (const std::vector<ProjectSymbol>& symbols) {
1954+ if (!suggestions) return ;
1955+
1956+ suggestions->ClearSymbols ();
1957+ suggestions->ClearInheritance ();
1958+
1959+ // Re-add engine API symbols
1960+ if (language == SyntaxLanguage::Lua || language == SyntaxLanguage::Cpp) {
1961+ addEngineAPISuggestions ();
1962+ }
1963+
1964+ // Add pre-parsed project symbols
1965+ for (const auto & sym : symbols) {
1966+ suggestions->AddSymbol (sym.name , static_cast <SuggestionKind>(sym.kind ), sym.detail , sym.parentType , sym.typeInfo );
1967+ }
1968+ }
1969+
18241970void CustomTextEditor::updateSuggestions () {
18251971 if (!suggestions) return ;
18261972
@@ -2715,7 +2861,21 @@ void CustomTextEditor::handleTextInput() {
27152861 }
27162862
27172863 // Update auto-complete
2718- if (autoComplete && std::isalnum (c)) {
2864+ bool shouldTrigger = false ;
2865+ if (autoComplete) {
2866+ if (std::isalnum (c) || c == ' _' || c == ' .' || c == ' :' ) {
2867+ shouldTrigger = true ;
2868+ } else if (c == ' >' && language == SyntaxLanguage::Cpp) {
2869+ // Trigger for '->' operator
2870+ const auto & pos = cursors[primaryCursor].position ;
2871+ if (pos.column >= 2 && pos.line < static_cast <int >(lines.size ())) {
2872+ if (lines[pos.line ][pos.column - 2 ] == ' -' ) {
2873+ shouldTrigger = true ;
2874+ }
2875+ }
2876+ }
2877+ }
2878+ if (shouldTrigger) {
27192879 TriggerAutoComplete ();
27202880 } else if (showAutoComplete) {
27212881 // Close autocomplete when typing non-identifier characters
@@ -3172,6 +3332,12 @@ void CustomTextEditor::Render(const char* title, const ImVec2& size, bool border
31723332 if (contentSize.y == 0 ) contentSize.y = ImGui::GetContentRegionAvail ().y ;
31733333
31743334 if (ImGui::BeginChild (title, contentSize, border ? ImGuiChildFlags_Borders : ImGuiChildFlags_None, flags)) {
3335+ // Auto-focus editor child when parent window is focused but child is not
3336+ // (e.g., user clicked the window tab/title bar)
3337+ if (!ImGui::IsWindowFocused () && ImGui::IsWindowFocused (ImGuiFocusedFlags_RootAndChildWindows)) {
3338+ ImGui::SetWindowFocus ();
3339+ }
3340+
31753341 if (pendingFocus) {
31763342 ImGui::SetWindowFocus ();
31773343 pendingFocus = false ;
0 commit comments