Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fb5976f123 | |||
| e4cd4877cc | |||
| ba9bd4a27d | |||
| fcb2e9a7ed | |||
| 38ba8c9871 | |||
| b91406860c | |||
| 8d1e9b2799 | |||
| c91fe214d6 | |||
| 99042f5ef1 | |||
| 96242154f7 |
16
.idea/codeStyles/Project.xml
generated
@@ -17,7 +17,6 @@
|
|||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue" value="2" type="int" />
|
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue" value="2" type="int" />
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_BLANK_LINES_IN_CODE/@EntryValue" value="2" type="int" />
|
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_BLANK_LINES_IN_CODE/@EntryValue" value="2" type="int" />
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_USER_LINEBREAKS/@EntryValue" value="true" type="bool" />
|
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_USER_LINEBREAKS/@EntryValue" value="true" type="bool" />
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_CASE_FROM_SWITCH/@EntryValue" />
|
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_COMMENT/@EntryValue" value="true" type="bool" />
|
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_COMMENT/@EntryValue" value="true" type="bool" />
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INT_ALIGN_EQ/@EntryValue" value="true" type="bool" />
|
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INT_ALIGN_EQ/@EntryValue" value="true" type="bool" />
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SIMPLE_BLOCK_STYLE/@EntryValue" value="LINE_BREAK" type="string" />
|
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SIMPLE_BLOCK_STYLE/@EntryValue" value="LINE_BREAK" type="string" />
|
||||||
@@ -115,33 +114,18 @@
|
|||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/TOPLEVEL_FUNCTION_DECLARATION_RETURN_TYPE_STYLE/@EntryValue" value="ON_SINGLE_LINE" type="string" />
|
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/TOPLEVEL_FUNCTION_DECLARATION_RETURN_TYPE_STYLE/@EntryValue" value="ON_SINGLE_LINE" type="string" />
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/FUNCTION_DECLARATION_RETURN_TYPE_STYLE/@EntryValue" value="ON_SINGLE_LINE" type="string" />
|
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/FUNCTION_DECLARATION_RETURN_TYPE_STYLE/@EntryValue" value="ON_SINGLE_LINE" type="string" />
|
||||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=0B82708A1BA7774EB13D27F245698A56/@EntryIndexedValue" value="<NamingElement Priority="1" Title="Classes and structs"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="__interface" /><type Name="class" /><type Name="struct" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=0B82708A1BA7774EB13D27F245698A56/@EntryIndexedValue" value="<NamingElement Priority="1" Title="Classes and structs"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="__interface" /><type Name="class" /><type Name="struct" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=0B82708A1BA7774EB13D27F245698A56/@EntryIndexRemoved" />
|
|
||||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=A7EBF16DA3BDCB42A0B710704BC8A053/@EntryIndexedValue" value="<NamingElement Priority="3" Title="Enums"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="enum" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=A7EBF16DA3BDCB42A0B710704BC8A053/@EntryIndexedValue" value="<NamingElement Priority="3" Title="Enums"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="enum" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=A7EBF16DA3BDCB42A0B710704BC8A053/@EntryIndexRemoved" />
|
|
||||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=0AFB7787612DF743B09AD9412E48D4CC/@EntryIndexedValue" value="<NamingElement Priority="7" Title="Local variables"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="local variable" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></NamingElement>" type="string" />
|
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=0AFB7787612DF743B09AD9412E48D4CC/@EntryIndexedValue" value="<NamingElement Priority="7" Title="Local variables"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="local variable" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></NamingElement>" type="string" />
|
||||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=0AFB7787612DF743B09AD9412E48D4CC/@EntryIndexRemoved" />
|
|
||||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=72514D5DF422D442B71A277F97B72887/@EntryIndexedValue" value="<NamingElement Priority="8" Title="Global variables"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="global variable" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AA_BB" /></NamingElement>" type="string" />
|
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=72514D5DF422D442B71A277F97B72887/@EntryIndexedValue" value="<NamingElement Priority="8" Title="Global variables"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="global variable" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AA_BB" /></NamingElement>" type="string" />
|
||||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=72514D5DF422D442B71A277F97B72887/@EntryIndexRemoved" />
|
|
||||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=821F3C5CF47D5640AD3511BCBADE17C4/@EntryIndexedValue" value="<NamingElement Priority="9" Title="Lambdas"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="lambda" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=821F3C5CF47D5640AD3511BCBADE17C4/@EntryIndexedValue" value="<NamingElement Priority="9" Title="Lambdas"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="lambda" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=821F3C5CF47D5640AD3511BCBADE17C4/@EntryIndexRemoved" />
|
|
||||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=8F69F48E2532F54CBAA0039D4557825E/@EntryIndexedValue" value="<NamingElement Priority="10" Title="Global functions"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="global function" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=8F69F48E2532F54CBAA0039D4557825E/@EntryIndexedValue" value="<NamingElement Priority="10" Title="Global functions"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="global function" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=8F69F48E2532F54CBAA0039D4557825E/@EntryIndexRemoved" />
|
|
||||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=B6E900853D6D05429D8C57765B2E546A/@EntryIndexedValue" value="<NamingElement Priority="11" Title="Class and struct methods"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="member function" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=B6E900853D6D05429D8C57765B2E546A/@EntryIndexedValue" value="<NamingElement Priority="11" Title="Class and struct methods"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="member function" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=B6E900853D6D05429D8C57765B2E546A/@EntryIndexRemoved" />
|
|
||||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=B82A063F0DDD98498A70D8D7EBB97F8D/@EntryIndexedValue" value="<NamingElement Priority="12" Title="Class and struct fields"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="class field" /><type Name="struct field" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="_" Style="aaBb" /></NamingElement>" type="string" />
|
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=B82A063F0DDD98498A70D8D7EBB97F8D/@EntryIndexedValue" value="<NamingElement Priority="12" Title="Class and struct fields"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="class field" /><type Name="struct field" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="_" Style="aaBb" /></NamingElement>" type="string" />
|
||||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=B82A063F0DDD98498A70D8D7EBB97F8D/@EntryIndexRemoved" />
|
|
||||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=BBE8AA08E662BF409B2CB08EC597C493/@EntryIndexedValue" value="<NamingElement Priority="13" Title="Class and struct public fields"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="PUBLIC"><type Name="class field" /><type Name="struct field" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></NamingElement>" type="string" />
|
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=BBE8AA08E662BF409B2CB08EC597C493/@EntryIndexedValue" value="<NamingElement Priority="13" Title="Class and struct public fields"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="PUBLIC"><type Name="class field" /><type Name="struct field" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></NamingElement>" type="string" />
|
||||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=BBE8AA08E662BF409B2CB08EC597C493/@EntryIndexRemoved" />
|
|
||||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=A4FAA2257682A94F8C2C93E123FAFC7A/@EntryIndexedValue" value="<NamingElement Priority="19" Title="Typedefs"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="type alias" /><type Name="typedef" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=A4FAA2257682A94F8C2C93E123FAFC7A/@EntryIndexedValue" value="<NamingElement Priority="19" Title="Typedefs"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="type alias" /><type Name="typedef" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=A4FAA2257682A94F8C2C93E123FAFC7A/@EntryIndexRemoved" />
|
|
||||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=BF0D1AE66D64FE4FAF613448A12051A0/@EntryIndexedValue" value="<NamingElement Priority="17" Title="Global constants"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="True" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="global variable" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=BF0D1AE66D64FE4FAF613448A12051A0/@EntryIndexedValue" value="<NamingElement Priority="17" Title="Global constants"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="True" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="global variable" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=BF0D1AE66D64FE4FAF613448A12051A0/@EntryIndexRemoved" />
|
|
||||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=2B232F1067F0324F8FF4B9D63ACECDB2/@EntryIndexedValue" value="<NamingElement Priority="16" Title="Other constants"><Descriptor Static="True" Constexpr="Indeterminate" Const="True" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="class field" /><type Name="local variable" /><type Name="struct field" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=2B232F1067F0324F8FF4B9D63ACECDB2/@EntryIndexedValue" value="<NamingElement Priority="16" Title="Other constants"><Descriptor Static="True" Constexpr="Indeterminate" Const="True" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="class field" /><type Name="local variable" /><type Name="struct field" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=2B232F1067F0324F8FF4B9D63ACECDB2/@EntryIndexRemoved" />
|
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/REMOVE_BLANK_LINES_NEAR_BRACES_IN_CODE/@EntryValue" />
|
|
||||||
<option name="/Default/CodeStyle/CppIncludeDirective/SortIncludeDirectives/@EntryValue" value="true" type="bool" />
|
<option name="/Default/CodeStyle/CppIncludeDirective/SortIncludeDirectives/@EntryValue" value="true" type="bool" />
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/USE_CONTINUOUS_LINE_INDENT_IN_METHOD_PARS/@EntryValue" />
|
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/USE_CONTINUOUS_LINE_INDENT_IN_EXPRESSION_BRACES/@EntryValue" />
|
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_GOTO_LABELS/@EntryValue" value="false" type="bool" />
|
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_GOTO_LABELS/@EntryValue" value="false" type="bool" />
|
||||||
</RiderCodeStyleSettings>
|
</RiderCodeStyleSettings>
|
||||||
<files>
|
<files>
|
||||||
|
|||||||
142
.idea/workspace.xml
generated
@@ -11,9 +11,9 @@
|
|||||||
<option name="/Default/Housekeeping/RefactoringsMru/RenameRefactoring/DoSearchForTextInStrings/@EntryValue" value="true" type="bool" />
|
<option name="/Default/Housekeeping/RefactoringsMru/RenameRefactoring/DoSearchForTextInStrings/@EntryValue" value="true" type="bool" />
|
||||||
<option name="/Default/RiderDebugger/RiderRestoreDecompile/RestoreDecompileSetting/@EntryValue" value="false" type="bool" />
|
<option name="/Default/RiderDebugger/RiderRestoreDecompile/RestoreDecompileSetting/@EntryValue" value="false" type="bool" />
|
||||||
</component>
|
</component>
|
||||||
<component name="CMakePresetLoader"><![CDATA[{
|
<component name="CMakePresetLoader">{
|
||||||
"useNewFormat": true
|
"useNewFormat": true
|
||||||
}]]></component>
|
}</component>
|
||||||
<component name="CMakeProjectFlavorService">
|
<component name="CMakeProjectFlavorService">
|
||||||
<option name="flavorId" value="CMakePlainProjectFlavor" />
|
<option name="flavorId" value="CMakePlainProjectFlavor" />
|
||||||
</component>
|
</component>
|
||||||
@@ -33,9 +33,10 @@
|
|||||||
</configurations>
|
</configurations>
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="e1fe3ab0-3650-4fca-8664-a247d5dfa457" name="Changes" comment="Add GUI initialization updates and improve navigation commands. - Implement terminal detachment for GUI mode to enable terminal closure post-launch. - Add `+N` support for opening files at specific line numbers and refine cursor positioning. - Introduce `JumpToLine` command for direct navigation by line number. - Enhance mouse wheel handling for line-wise scrolling.">
|
<list default="true" id="e1fe3ab0-3650-4fca-8664-a247d5dfa457" name="Changes" comment="Introduce file picker and GUI configuration with enhancements. - Add visual file picker for GUI with toggle support. - Introduce `GUIConfig` class for loading GUI settings from configuration file. - Refactor window initialization to support dynamic sizing based on configuration. - Add macOS-specific handling for fullscreen behavior. - Improve header inclusion order and minor code cleanup.">
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/CMakeLists.txt" beforeDir="false" afterPath="$PROJECT_DIR$/CMakeLists.txt" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/CMakeLists.txt" beforeDir="false" afterPath="$PROJECT_DIR$/CMakeLists.txt" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/docs/kte.1" beforeDir="false" afterPath="$PROJECT_DIR$/docs/kte.1" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -56,15 +57,6 @@
|
|||||||
<component name="HighlightingSettingsPerFile">
|
<component name="HighlightingSettingsPerFile">
|
||||||
<setting file="mock:///AIAssistantSnippet.." root0="SKIP_HIGHLIGHTING" />
|
<setting file="mock:///AIAssistantSnippet.." root0="SKIP_HIGHLIGHTING" />
|
||||||
<setting file="mock:///AIAssistantSnippet.." root0="SKIP_HIGHLIGHTING" />
|
<setting file="mock:///AIAssistantSnippet.." root0="SKIP_HIGHLIGHTING" />
|
||||||
<setting file="mock:///AIAssistantSnippet.." root0="SKIP_HIGHLIGHTING" />
|
|
||||||
<setting file="mock:///dummy.cpp" root0="SKIP_HIGHLIGHTING" />
|
|
||||||
<setting file="mock:///dummy.cpp" root0="SKIP_HIGHLIGHTING" />
|
|
||||||
<setting file="mock:///dummy.cpp" root0="SKIP_HIGHLIGHTING" />
|
|
||||||
<setting file="mock:///dummy.cpp" root0="SKIP_HIGHLIGHTING" />
|
|
||||||
<setting file="mock:///dummy.cpp" root0="SKIP_HIGHLIGHTING" />
|
|
||||||
<setting file="mock:///dummy.cpp" root0="SKIP_HIGHLIGHTING" />
|
|
||||||
<setting file="mock:///dummy.cpp" root0="SKIP_HIGHLIGHTING" />
|
|
||||||
<setting file="mock:///dummy.cpp" root0="SKIP_HIGHLIGHTING" />
|
|
||||||
</component>
|
</component>
|
||||||
<component name="OptimizeOnSaveOptions">
|
<component name="OptimizeOnSaveOptions">
|
||||||
<option name="myRunOnSave" value="true" />
|
<option name="myRunOnSave" value="true" />
|
||||||
@@ -75,9 +67,9 @@
|
|||||||
<option name="minorVersion" value="2.5" />
|
<option name="minorVersion" value="2.5" />
|
||||||
<option name="productBranch" value="Classic" />
|
<option name="productBranch" value="Classic" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectColorInfo"><![CDATA[{
|
<component name="ProjectColorInfo">{
|
||||||
"associatedIndex": 3
|
"associatedIndex": 3
|
||||||
}]]></component>
|
}</component>
|
||||||
<component name="ProjectId" id="36AlI8oyQOzOwSuZg6WxXf5LbHb" />
|
<component name="ProjectId" id="36AlI8oyQOzOwSuZg6WxXf5LbHb" />
|
||||||
<component name="ProjectLevelVcsManager">
|
<component name="ProjectLevelVcsManager">
|
||||||
<OptionsSetting value="false" id="Update" />
|
<OptionsSetting value="false" id="Update" />
|
||||||
@@ -88,51 +80,52 @@
|
|||||||
<option name="sortByType" value="true" />
|
<option name="sortByType" value="true" />
|
||||||
<option name="sortKey" value="BY_TYPE" />
|
<option name="sortKey" value="BY_TYPE" />
|
||||||
</component>
|
</component>
|
||||||
<component name="PropertiesComponent"><![CDATA[{
|
<component name="PropertiesComponent">{
|
||||||
"keyToString": {
|
"keyToString": {
|
||||||
"CMake Application.kge.executor": "Run",
|
"CMake Application.kge.executor": "Run",
|
||||||
"CMake Application.test_example.executor": "Run",
|
"CMake Application.test_example.executor": "Run",
|
||||||
"CMake Application.test_undo.executor": "Run",
|
"CMake Application.test_undo.executor": "Run",
|
||||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||||
"NIXITCH_NIXPKGS_CONFIG": "",
|
"NIXITCH_NIXPKGS_CONFIG": "",
|
||||||
"NIXITCH_NIX_CONF_DIR": "",
|
"NIXITCH_NIX_CONF_DIR": "",
|
||||||
"NIXITCH_NIX_OTHER_STORES": "",
|
"NIXITCH_NIX_OTHER_STORES": "",
|
||||||
"NIXITCH_NIX_PATH": "",
|
"NIXITCH_NIX_PATH": "",
|
||||||
"NIXITCH_NIX_PROFILES": "",
|
"NIXITCH_NIX_PROFILES": "",
|
||||||
"NIXITCH_NIX_REMOTE": "",
|
"NIXITCH_NIX_REMOTE": "",
|
||||||
"NIXITCH_NIX_USER_PROFILE_DIR": "",
|
"NIXITCH_NIX_USER_PROFILE_DIR": "",
|
||||||
"RunOnceActivity.RadMigrateCodeStyle": "true",
|
"RunOnceActivity.RadMigrateCodeStyle": "true",
|
||||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
"RunOnceActivity.cidr.known.project.marker": "true",
|
"RunOnceActivity.cidr.known.project.marker": "true",
|
||||||
"RunOnceActivity.readMode.enableVisualFormatting": "true",
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
"RunOnceActivity.west.config.association.type.startup.service": "true",
|
"RunOnceActivity.readMode.enableVisualFormatting": "true",
|
||||||
"cf.first.check.clang-format": "false",
|
"RunOnceActivity.west.config.association.type.startup.service": "true",
|
||||||
"cidr.known.project.marker": "true",
|
"cf.first.check.clang-format": "false",
|
||||||
"code.cleanup.on.save": "true",
|
"cidr.known.project.marker": "true",
|
||||||
"com.intellij.ml.llm.matterhorn.ej.ui.settings.DefaultModelSelectionForGA.v1": "true",
|
"code.cleanup.on.save": "true",
|
||||||
"git-widget-placeholder": "master",
|
"com.intellij.ml.llm.matterhorn.ej.ui.settings.DefaultModelSelectionForGA.v1": "true",
|
||||||
"junie.onboarding.icon.badge.shown": "true",
|
"git-widget-placeholder": "master",
|
||||||
"node.js.detected.package.eslint": "true",
|
"junie.onboarding.icon.badge.shown": "true",
|
||||||
"node.js.detected.package.tslint": "true",
|
"node.js.detected.package.eslint": "true",
|
||||||
"node.js.selected.package.eslint": "(autodetect)",
|
"node.js.detected.package.tslint": "true",
|
||||||
"node.js.selected.package.tslint": "(autodetect)",
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
"nodejs_package_manager_path": "npm",
|
"node.js.selected.package.tslint": "(autodetect)",
|
||||||
"onboarding.tips.debug.path": "/Users/kyle/src/kte/main.cpp",
|
"nodejs_package_manager_path": "npm",
|
||||||
"rearrange.code.on.save": "true",
|
"onboarding.tips.debug.path": "/Users/kyle/src/kte/main.cpp",
|
||||||
"settings.editor.selected.configurable": "junie.application.models",
|
"rearrange.code.on.save": "true",
|
||||||
"to.speed.mode.migration.done": "true",
|
"settings.editor.selected.configurable": "editor.preferences.fonts.default",
|
||||||
"vue.rearranger.settings.migration": "true"
|
"to.speed.mode.migration.done": "true",
|
||||||
|
"vue.rearranger.settings.migration": "true"
|
||||||
}
|
}
|
||||||
}]]></component>
|
}</component>
|
||||||
<component name="RecentsManager">
|
<component name="RecentsManager">
|
||||||
<key name="MoveFile.RECENT_KEYS">
|
<key name="MoveFile.RECENT_KEYS">
|
||||||
<recent name="$PROJECT_DIR$/docs" />
|
<recent name="$PROJECT_DIR$/docs" />
|
||||||
</key>
|
</key>
|
||||||
</component>
|
</component>
|
||||||
<component name="RunManager" selected="CMake Application.kge">
|
<component name="RunManager" selected="CMake Application.kge">
|
||||||
<configuration default="true" type="CMakeRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true">
|
<configuration default="true" type="CLionExternalRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true">
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
|
<option name="CLION.EXTERNAL.BUILD" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
</configuration>
|
</configuration>
|
||||||
<configuration name="imgui" type="CMakeRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="kte" TARGET_NAME="imgui" CONFIG_NAME="Debug">
|
<configuration name="imgui" type="CMakeRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="kte" TARGET_NAME="imgui" CONFIG_NAME="Debug">
|
||||||
@@ -140,7 +133,7 @@
|
|||||||
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
|
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
</configuration>
|
</configuration>
|
||||||
<configuration name="kge" type="CMakeRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="kte" TARGET_NAME="kge" CONFIG_NAME="Debug" RUN_TARGET_PROJECT_NAME="kte" RUN_TARGET_NAME="kge">
|
<configuration name="kge" type="CMakeRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$PROJECT_DIR$" PASS_PARENT_ENVS_2="true" PROJECT_NAME="kte" TARGET_NAME="kge" CONFIG_NAME="Debug" RUN_TARGET_PROJECT_NAME="kte" RUN_TARGET_NAME="kge">
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
|
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
@@ -164,6 +157,14 @@
|
|||||||
<option name="presentableId" value="Default" />
|
<option name="presentableId" value="Default" />
|
||||||
<updated>1764457173148</updated>
|
<updated>1764457173148</updated>
|
||||||
<workItem from="1764457174208" duration="46950000" />
|
<workItem from="1764457174208" duration="46950000" />
|
||||||
|
<workItem from="1764538560497" duration="215000" />
|
||||||
|
<workItem from="1764539255906" duration="196000" />
|
||||||
|
<workItem from="1764539459951" duration="64000" />
|
||||||
|
<workItem from="1764539535105" duration="10000" />
|
||||||
|
<workItem from="1764539556448" duration="156000" />
|
||||||
|
<workItem from="1764539725338" duration="1075000" />
|
||||||
|
<workItem from="1764542392763" duration="3512000" />
|
||||||
|
<workItem from="1764548345516" duration="8203000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="Add undo/redo infrastructure and buffer management additions.">
|
<task id="LOCAL-00001" summary="Add undo/redo infrastructure and buffer management additions.">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
@@ -237,7 +238,31 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1764505723411</updated>
|
<updated>1764505723411</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="10" />
|
<task id="LOCAL-00010" summary="Refactor code for consistency and enhanced functionality. - Normalize path handling for buffer operations, supporting tilde expansion and absolute paths. - Introduce `DisplayNameFor` to uniquely resolve buffer display names, minimizing filename clashes. - Add new commands: `ShowWorkingDirectory` and `ChangeWorkingDirectory`. - Refine keybindings and enhance existing commands for improved command flow. - Adjust GUI and terminal renderers to display total line counts alongside filenames. - Update coding style to align with project guidelines.">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1764550164829</created>
|
||||||
|
<option name="number" value="00010" />
|
||||||
|
<option name="presentableId" value="LOCAL-00010" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1764550164829</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00011" summary="Add horizontal scrolling support and refactor mouse click handling in GUI. - Introduce horizontal scrolling with column offset synchronization in GUI. - Refactor mouse click handling for improved accuracy and viewport alignment. - Enhance tab expansion and cursor rendering logic for better user experience. - Replace redundant variable declarations in `Buffer` for cleaner code.">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1764551986561</created>
|
||||||
|
<option name="number" value="00011" />
|
||||||
|
<option name="presentableId" value="LOCAL-00011" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1764551986561</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00012" summary="Introduce file picker and GUI configuration with enhancements. - Add visual file picker for GUI with toggle support. - Introduce `GUIConfig` class for loading GUI settings from configuration file. - Refactor window initialization to support dynamic sizing based on configuration. - Add macOS-specific handling for fullscreen behavior. - Improve header inclusion order and minor code cleanup.">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1764556512864</created>
|
||||||
|
<option name="number" value="00012" />
|
||||||
|
<option name="presentableId" value="LOCAL-00012" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1764556512864</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="13" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -260,7 +285,10 @@
|
|||||||
<MESSAGE value="Remove `packaging.cmake`, deprecate `test_undo` setup, and add new testing infrastructure. - Delete `packaging.cmake` to streamline build system. - Deprecate `test_undo` in CMake setup; condition builds on `BUILD_TESTS`. - Introduce `TestFrontend`, `TestRenderer`, and `TestInputHandler` for structured testing. - Update `GUIInputHandler` and `Command` for enhanced buffer save handling and overwrite confirmation. - Enhance kill ring operations and new prompt workflows in `Editor`." />
|
<MESSAGE value="Remove `packaging.cmake`, deprecate `test_undo` setup, and add new testing infrastructure. - Delete `packaging.cmake` to streamline build system. - Deprecate `test_undo` in CMake setup; condition builds on `BUILD_TESTS`. - Introduce `TestFrontend`, `TestRenderer`, and `TestInputHandler` for structured testing. - Update `GUIInputHandler` and `Command` for enhanced buffer save handling and overwrite confirmation. - Enhance kill ring operations and new prompt workflows in `Editor`." />
|
||||||
<MESSAGE value="Add man pages for `kge` and `kte` with installation targets in CMake. - Introduce `docs/kge.1` and `docs/kte.1` man pages covering usage, options, keybindings, and examples. - Update `CMakeLists.txt` to install man pages under `${CMAKE_INSTALL_MANDIR}/man1`. - Ensure `kge` man page installation is conditional on GUI being built." />
|
<MESSAGE value="Add man pages for `kge` and `kte` with installation targets in CMake. - Introduce `docs/kge.1` and `docs/kte.1` man pages covering usage, options, keybindings, and examples. - Update `CMakeLists.txt` to install man pages under `${CMAKE_INSTALL_MANDIR}/man1`. - Ensure `kge` man page installation is conditional on GUI being built." />
|
||||||
<MESSAGE value="Add GUI initialization updates and improve navigation commands. - Implement terminal detachment for GUI mode to enable terminal closure post-launch. - Add `+N` support for opening files at specific line numbers and refine cursor positioning. - Introduce `JumpToLine` command for direct navigation by line number. - Enhance mouse wheel handling for line-wise scrolling." />
|
<MESSAGE value="Add GUI initialization updates and improve navigation commands. - Implement terminal detachment for GUI mode to enable terminal closure post-launch. - Add `+N` support for opening files at specific line numbers and refine cursor positioning. - Introduce `JumpToLine` command for direct navigation by line number. - Enhance mouse wheel handling for line-wise scrolling." />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="Add GUI initialization updates and improve navigation commands. - Implement terminal detachment for GUI mode to enable terminal closure post-launch. - Add `+N` support for opening files at specific line numbers and refine cursor positioning. - Introduce `JumpToLine` command for direct navigation by line number. - Enhance mouse wheel handling for line-wise scrolling." />
|
<MESSAGE value="Refactor code for consistency and enhanced functionality. - Normalize path handling for buffer operations, supporting tilde expansion and absolute paths. - Introduce `DisplayNameFor` to uniquely resolve buffer display names, minimizing filename clashes. - Add new commands: `ShowWorkingDirectory` and `ChangeWorkingDirectory`. - Refine keybindings and enhance existing commands for improved command flow. - Adjust GUI and terminal renderers to display total line counts alongside filenames. - Update coding style to align with project guidelines." />
|
||||||
|
<MESSAGE value="Add horizontal scrolling support and refactor mouse click handling in GUI. - Introduce horizontal scrolling with column offset synchronization in GUI. - Refactor mouse click handling for improved accuracy and viewport alignment. - Enhance tab expansion and cursor rendering logic for better user experience. - Replace redundant variable declarations in `Buffer` for cleaner code." />
|
||||||
|
<MESSAGE value="Introduce file picker and GUI configuration with enhancements. - Add visual file picker for GUI with toggle support. - Introduce `GUIConfig` class for loading GUI settings from configuration file. - Refactor window initialization to support dynamic sizing based on configuration. - Add macOS-specific handling for fullscreen behavior. - Improve header inclusion order and minor code cleanup." />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="Introduce file picker and GUI configuration with enhancements. - Add visual file picker for GUI with toggle support. - Introduce `GUIConfig` class for loading GUI settings from configuration file. - Refactor window initialization to support dynamic sizing based on configuration. - Add macOS-specific handling for fullscreen behavior. - Improve header inclusion order and minor code cleanup." />
|
||||||
</component>
|
</component>
|
||||||
<component name="XSLT-Support.FileAssociations.UIState">
|
<component name="XSLT-Support.FileAssociations.UIState">
|
||||||
<expand />
|
<expand />
|
||||||
|
|||||||
@@ -1,39 +1,19 @@
|
|||||||
# Project Guidelines
|
# Project Guidelines
|
||||||
|
|
||||||
kte is Kyle's Text Editor — a simple, fast text editor written in C++17. It
|
kte is Kyle's Text Editor — a simple, fast text editor written in C++17. It
|
||||||
replaces the earlier C implementation, ke (see the ke manual in `ke.md`). The
|
replaces the earlier C implementation, ke (see the ke manual in `docs/ke.md`). The
|
||||||
design draws inspiration from Antirez' kilo, with keybindings rooted in the
|
design draws inspiration from Antirez' kilo, with keybindings rooted in the
|
||||||
WordStar/VDE family and emacs. The spiritual parent is `mg(1)`.
|
WordStar/VDE family and emacs. The spiritual parent is `mg(1)`.
|
||||||
|
|
||||||
These guidelines summarize the goals, interfaces, key operations, and current
|
These guidelines summarize the goals, interfaces, key operations, and current
|
||||||
development practices for kte.
|
development practices for kte.
|
||||||
|
|
||||||
Style note: all code should be formatted with the current CLion C++ style.
|
|
||||||
|
|
||||||
## Goals
|
## Goals
|
||||||
|
|
||||||
- Keep the core small, fast, and understandable.
|
- Keep the core small, fast, and understandable.
|
||||||
- Provide an ncurses-based terminal-first editing experience, with an optional ImGui GUI.
|
- Provide an ncurses-based terminal-first editing experience, with an additional ImGui GUI.
|
||||||
- Preserve familiar keybindings from ke while modernizing the internals.
|
- Preserve familiar keybindings from ke while modernizing the internals.
|
||||||
- Favor simple data structures (e.g., gap buffer) and incremental evolution.
|
- Favor simple data structures (e.g., piece table) and incremental evolution.
|
||||||
|
|
||||||
## Interfaces
|
|
||||||
|
|
||||||
- Command-line interface: the primary interface today.
|
|
||||||
- GUI: planned ImGui-based interface.
|
|
||||||
|
|
||||||
## Build and Run
|
|
||||||
|
|
||||||
Prerequisites: a C++17 compiler, CMake, and ncurses development headers/libs.
|
|
||||||
|
|
||||||
- macOS (Homebrew): `brew install ncurses`
|
|
||||||
- Debian/Ubuntu: `sudo apt-get install libncurses5-dev libncursesw5-dev`
|
|
||||||
|
|
||||||
- Configure and build (example):
|
|
||||||
- `cmake -S . -B cmake-build-debug -DCMAKE_BUILD_TYPE=Debug`
|
|
||||||
- `cmake --build cmake-build-debug`
|
|
||||||
- Run:
|
|
||||||
- `./cmake-build-debug/kte [files]`
|
|
||||||
|
|
||||||
Project entry point: `main.cpp`
|
Project entry point: `main.cpp`
|
||||||
|
|
||||||
@@ -52,31 +32,12 @@ Project entry point: `main.cpp`
|
|||||||
|
|
||||||
## Keybindings (inherited from ke)
|
## Keybindings (inherited from ke)
|
||||||
|
|
||||||
kte aims to maintain ke’s command model while internals evolve. See `ke.md` for
|
The file `docs/ke.md` contains the canonical reference for keybindings.
|
||||||
the full reference. Highlights:
|
|
||||||
|
|
||||||
- K-command prefix: `C-k` enters k-command mode; exit with `ESC` or `C-g`.
|
|
||||||
- Save/Exit: `C-k s` (save), `C-k x` or `C-k C-x` (save and exit), `C-k q` (quit
|
|
||||||
with confirm), `C-k C-q` (quit immediately).
|
|
||||||
- Editing: `C-k d` (kill to EOL), `C-k C-d` (kill line), `C-k BACKSPACE` (kill
|
|
||||||
to BOL), `C-w` (kill region), `C-y` (yank), `C-u` (universal argument).
|
|
||||||
- Navigation/Search: `C-s` (incremental find), `C-r` (regex search), `ESC f/b`
|
|
||||||
(word next/prev), `ESC BACKSPACE` (delete previous word).
|
|
||||||
- Buffers/Files: `C-k e` (open), `C-k b`/`C-k p` (switch), `C-k c` (close),
|
|
||||||
`C-k C-r` (reload).
|
|
||||||
- Misc: `C-l` (refresh), `C-g` (cancel), `C-k m` (run make), `C-k g` (goto line).
|
|
||||||
|
|
||||||
Known behavior from ke retained for now:
|
|
||||||
|
|
||||||
- Incremental search navigates results with arrow keys; search restarts from
|
|
||||||
the top on each invocation (known bug to be revisited).
|
|
||||||
|
|
||||||
## Contributing/Development Notes
|
## Contributing/Development Notes
|
||||||
|
|
||||||
- C++ standard: C++17.
|
- C++ standard: C++17.
|
||||||
- Style: match existing file formatting and minimal-comment style.
|
- Keep dependencies minimal.
|
||||||
- Keep dependencies minimal; ImGui integration will be isolated behind a GUI
|
|
||||||
module.
|
|
||||||
- Prefer small, focused changes that preserve ke’s UX unless explicitly changing
|
- Prefer small, focused changes that preserve ke’s UX unless explicitly changing
|
||||||
behavior.
|
behavior.
|
||||||
|
|
||||||
|
|||||||
111
Buffer.cc
@@ -1,10 +1,11 @@
|
|||||||
#include "Buffer.h"
|
|
||||||
#include "UndoSystem.h"
|
|
||||||
#include "UndoTree.h"
|
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#include "Buffer.h"
|
||||||
|
#include "UndoSystem.h"
|
||||||
|
#include "UndoTree.h"
|
||||||
|
|
||||||
|
|
||||||
Buffer::Buffer()
|
Buffer::Buffer()
|
||||||
@@ -129,12 +130,36 @@ Buffer::operator=(Buffer &&other) noexcept
|
|||||||
bool
|
bool
|
||||||
Buffer::OpenFromFile(const std::string &path, std::string &err)
|
Buffer::OpenFromFile(const std::string &path, std::string &err)
|
||||||
{
|
{
|
||||||
|
auto normalize_path = [](const std::string &in) -> std::string {
|
||||||
|
std::string expanded = in;
|
||||||
|
// Expand leading '~' to HOME
|
||||||
|
if (!expanded.empty() && expanded[0] == '~') {
|
||||||
|
const char *home = std::getenv("HOME");
|
||||||
|
if (home && expanded.size() >= 2 && (expanded[1] == '/' || expanded[1] == '\\')) {
|
||||||
|
expanded = std::string(home) + expanded.substr(1);
|
||||||
|
} else if (home && expanded.size() == 1) {
|
||||||
|
expanded = std::string(home);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
std::filesystem::path p(expanded);
|
||||||
|
if (std::filesystem::exists(p)) {
|
||||||
|
return std::filesystem::canonical(p).string();
|
||||||
|
}
|
||||||
|
return std::filesystem::absolute(p).string();
|
||||||
|
} catch (...) {
|
||||||
|
// On any error, fall back to input
|
||||||
|
return expanded;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::string norm = normalize_path(path);
|
||||||
// If the file doesn't exist, initialize an empty, non-file-backed buffer
|
// If the file doesn't exist, initialize an empty, non-file-backed buffer
|
||||||
// with the provided filename. Do not touch the filesystem until Save/SaveAs.
|
// with the provided filename. Do not touch the filesystem until Save/SaveAs.
|
||||||
if (!std::filesystem::exists(path)) {
|
if (!std::filesystem::exists(norm)) {
|
||||||
rows_.clear();
|
rows_.clear();
|
||||||
nrows_ = 0;
|
nrows_ = 0;
|
||||||
filename_ = path;
|
filename_ = norm;
|
||||||
is_file_backed_ = false;
|
is_file_backed_ = false;
|
||||||
dirty_ = false;
|
dirty_ = false;
|
||||||
|
|
||||||
@@ -147,9 +172,9 @@ Buffer::OpenFromFile(const std::string &path, std::string &err)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ifstream in(path, std::ios::in | std::ios::binary);
|
std::ifstream in(norm, std::ios::in | std::ios::binary);
|
||||||
if (!in) {
|
if (!in) {
|
||||||
err = "Failed to open file: " + path;
|
err = "Failed to open file: " + norm;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +219,7 @@ Buffer::OpenFromFile(const std::string &path, std::string &err)
|
|||||||
}
|
}
|
||||||
|
|
||||||
nrows_ = rows_.size();
|
nrows_ = rows_.size();
|
||||||
filename_ = path;
|
filename_ = norm;
|
||||||
is_file_backed_ = true;
|
is_file_backed_ = true;
|
||||||
dirty_ = false;
|
dirty_ = false;
|
||||||
|
|
||||||
@@ -250,10 +275,29 @@ Buffer::Save(std::string &err) const
|
|||||||
bool
|
bool
|
||||||
Buffer::SaveAs(const std::string &path, std::string &err)
|
Buffer::SaveAs(const std::string &path, std::string &err)
|
||||||
{
|
{
|
||||||
|
// Normalize output path first
|
||||||
|
std::string out_path;
|
||||||
|
try {
|
||||||
|
std::filesystem::path p(path);
|
||||||
|
// Do a light expansion of '~'
|
||||||
|
std::string expanded = path;
|
||||||
|
if (!expanded.empty() && expanded[0] == '~') {
|
||||||
|
const char *home = std::getenv("HOME");
|
||||||
|
if (home && expanded.size() >= 2 && (expanded[1] == '/' || expanded[1] == '\\'))
|
||||||
|
expanded = std::string(home) + expanded.substr(1);
|
||||||
|
else if (home && expanded.size() == 1)
|
||||||
|
expanded = std::string(home);
|
||||||
|
}
|
||||||
|
std::filesystem::path ep(expanded);
|
||||||
|
out_path = std::filesystem::absolute(ep).string();
|
||||||
|
} catch (...) {
|
||||||
|
out_path = path;
|
||||||
|
}
|
||||||
|
|
||||||
// Write to the given path
|
// Write to the given path
|
||||||
std::ofstream out(path, std::ios::out | std::ios::binary | std::ios::trunc);
|
std::ofstream out(out_path, std::ios::out | std::ios::binary | std::ios::trunc);
|
||||||
if (!out) {
|
if (!out) {
|
||||||
err = "Failed to open for write: " + path;
|
err = "Failed to open for write: " + out_path;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (std::size_t i = 0; i < rows_.size(); ++i) {
|
for (std::size_t i = 0; i < rows_.size(); ++i) {
|
||||||
@@ -270,7 +314,7 @@ Buffer::SaveAs(const std::string &path, std::string &err)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
filename_ = path;
|
filename_ = out_path;
|
||||||
is_file_backed_ = true;
|
is_file_backed_ = true;
|
||||||
dirty_ = false;
|
dirty_ = false;
|
||||||
return true;
|
return true;
|
||||||
@@ -303,8 +347,8 @@ Buffer::insert_text(int row, int col, std::string_view text)
|
|||||||
if (static_cast<std::size_t>(row) >= rows_.size())
|
if (static_cast<std::size_t>(row) >= rows_.size())
|
||||||
rows_.emplace_back("");
|
rows_.emplace_back("");
|
||||||
|
|
||||||
std::size_t y = static_cast<std::size_t>(row);
|
auto y = static_cast<std::size_t>(row);
|
||||||
std::size_t x = static_cast<std::size_t>(col);
|
auto x = static_cast<std::size_t>(col);
|
||||||
if (x > rows_[y].size())
|
if (x > rows_[y].size())
|
||||||
x = rows_[y].size();
|
x = rows_[y].size();
|
||||||
|
|
||||||
@@ -340,13 +384,13 @@ Buffer::delete_text(int row, int col, std::size_t len)
|
|||||||
row = 0;
|
row = 0;
|
||||||
if (static_cast<std::size_t>(row) >= rows_.size())
|
if (static_cast<std::size_t>(row) >= rows_.size())
|
||||||
return;
|
return;
|
||||||
std::size_t y = static_cast<std::size_t>(row);
|
const auto y = static_cast<std::size_t>(row);
|
||||||
std::size_t x = std::min<std::size_t>(static_cast<std::size_t>(col), rows_[y].size());
|
const auto x = std::min<std::size_t>(static_cast<std::size_t>(col), rows_[y].size());
|
||||||
|
|
||||||
std::size_t remaining = len;
|
std::size_t remaining = len;
|
||||||
while (remaining > 0 && y < rows_.size()) {
|
while (remaining > 0 && y < rows_.size()) {
|
||||||
auto &line = rows_[y];
|
auto &line = rows_[y];
|
||||||
std::size_t in_line = std::min<std::size_t>(remaining, line.size() - std::min(x, line.size()));
|
const std::size_t in_line = std::min<std::size_t>(remaining, line.size() - std::min(x, line.size()));
|
||||||
if (x < line.size() && in_line > 0) {
|
if (x < line.size() && in_line > 0) {
|
||||||
line.erase(x, in_line);
|
line.erase(x, in_line);
|
||||||
remaining -= in_line;
|
remaining -= in_line;
|
||||||
@@ -370,15 +414,18 @@ Buffer::delete_text(int row, int col, std::size_t len)
|
|||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
Buffer::split_line(int row, int col)
|
Buffer::split_line(int row, const int col)
|
||||||
{
|
{
|
||||||
if (row < 0)
|
if (row < 0) {
|
||||||
row = 0;
|
row = 0;
|
||||||
if (static_cast<std::size_t>(row) >= rows_.size())
|
}
|
||||||
|
|
||||||
|
if (static_cast<std::size_t>(row) >= rows_.size()) {
|
||||||
rows_.resize(static_cast<std::size_t>(row) + 1);
|
rows_.resize(static_cast<std::size_t>(row) + 1);
|
||||||
std::size_t y = static_cast<std::size_t>(row);
|
}
|
||||||
std::size_t x = std::min<std::size_t>(static_cast<std::size_t>(col), rows_[y].size());
|
const auto y = static_cast<std::size_t>(row);
|
||||||
std::string tail = rows_[y].substr(x);
|
const auto x = std::min<std::size_t>(static_cast<std::size_t>(col), rows_[y].size());
|
||||||
|
const auto tail = rows_[y].substr(x);
|
||||||
rows_[y].erase(x);
|
rows_[y].erase(x);
|
||||||
rows_.insert(rows_.begin() + static_cast<std::ptrdiff_t>(y + 1), tail);
|
rows_.insert(rows_.begin() + static_cast<std::ptrdiff_t>(y + 1), tail);
|
||||||
}
|
}
|
||||||
@@ -387,24 +434,28 @@ Buffer::split_line(int row, int col)
|
|||||||
void
|
void
|
||||||
Buffer::join_lines(int row)
|
Buffer::join_lines(int row)
|
||||||
{
|
{
|
||||||
if (row < 0)
|
if (row < 0) {
|
||||||
row = 0;
|
row = 0;
|
||||||
std::size_t y = static_cast<std::size_t>(row);
|
}
|
||||||
if (y + 1 >= rows_.size())
|
|
||||||
|
const auto y = static_cast<std::size_t>(row);
|
||||||
|
if (y + 1 >= rows_.size()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
rows_[y] += rows_[y + 1];
|
rows_[y] += rows_[y + 1];
|
||||||
rows_.erase(rows_.begin() + static_cast<std::ptrdiff_t>(y + 1));
|
rows_.erase(rows_.begin() + static_cast<std::ptrdiff_t>(y + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
Buffer::insert_row(int row, std::string_view text)
|
Buffer::insert_row(int row, const std::string_view text)
|
||||||
{
|
{
|
||||||
if (row < 0)
|
if (row < 0)
|
||||||
row = 0;
|
row = 0;
|
||||||
if (static_cast<std::size_t>(row) > rows_.size())
|
if (static_cast<std::size_t>(row) > rows_.size())
|
||||||
row = static_cast<int>(rows_.size());
|
row = static_cast<int>(rows_.size());
|
||||||
rows_.insert(rows_.begin() + static_cast<std::ptrdiff_t>(row), std::string(text));
|
rows_.insert(rows_.begin() + row, std::string(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -415,7 +466,7 @@ Buffer::delete_row(int row)
|
|||||||
row = 0;
|
row = 0;
|
||||||
if (static_cast<std::size_t>(row) >= rows_.size())
|
if (static_cast<std::size_t>(row) >= rows_.size())
|
||||||
return;
|
return;
|
||||||
rows_.erase(rows_.begin() + static_cast<std::ptrdiff_t>(row));
|
rows_.erase(rows_.begin() + row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
1
Buffer.h
@@ -12,6 +12,7 @@
|
|||||||
#include "AppendBuffer.h"
|
#include "AppendBuffer.h"
|
||||||
#include "UndoSystem.h"
|
#include "UndoSystem.h"
|
||||||
|
|
||||||
|
|
||||||
class Buffer {
|
class Buffer {
|
||||||
public:
|
public:
|
||||||
Buffer();
|
Buffer();
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ project(kte)
|
|||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(KTE_VERSION "0.9.0")
|
set(KTE_VERSION "1.0.0")
|
||||||
|
|
||||||
# Default to terminal-only build to avoid SDL/OpenGL dependency by default.
|
# Default to terminal-only build to avoid SDL/OpenGL dependency by default.
|
||||||
# Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available.
|
# Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available.
|
||||||
set(BUILD_GUI OFF CACHE BOOL "Enable building the graphical version.")
|
set(BUILD_GUI OFF CACHE BOOL "Enable building the graphical version.")
|
||||||
set(BUILD_TESTS OFF CACHE BOOL "Enable building test programs.")
|
set(BUILD_TESTS OFF CACHE BOOL "Enable building test programs.")
|
||||||
option(KTE_USE_PIECE_TABLE "Use PieceTable instead of GapBuffer implementation" OFF)
|
option(KTE_USE_PIECE_TABLE "Use PieceTable instead of GapBuffer implementation" ON)
|
||||||
set(KTE_FONT_SIZE "18.0" CACHE STRING "Default font size for GUI")
|
set(KTE_FONT_SIZE "18.0" CACHE STRING "Default font size for GUI")
|
||||||
|
|
||||||
if (CMAKE_HOST_UNIX)
|
if (CMAKE_HOST_UNIX)
|
||||||
@@ -44,6 +44,8 @@ if (${BUILD_GUI})
|
|||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
# NCurses for terminal mode
|
# NCurses for terminal mode
|
||||||
|
set(CURSES_NEED_NCURSES)
|
||||||
|
set(CURSES_NEED_WIDE)
|
||||||
find_package(Curses REQUIRED)
|
find_package(Curses REQUIRED)
|
||||||
include_directories(${CURSES_INCLUDE_DIR})
|
include_directories(${CURSES_INCLUDE_DIR})
|
||||||
|
|
||||||
@@ -126,6 +128,8 @@ endif ()
|
|||||||
if (${BUILD_GUI})
|
if (${BUILD_GUI})
|
||||||
target_sources(kte PRIVATE
|
target_sources(kte PRIVATE
|
||||||
Font.h
|
Font.h
|
||||||
|
GUIConfig.cc
|
||||||
|
GUIConfig.h
|
||||||
GUIRenderer.cc
|
GUIRenderer.cc
|
||||||
GUIRenderer.h
|
GUIRenderer.h
|
||||||
GUIInputHandler.cc
|
GUIInputHandler.cc
|
||||||
@@ -140,6 +144,8 @@ if (${BUILD_GUI})
|
|||||||
main.cc
|
main.cc
|
||||||
${COMMON_SOURCES}
|
${COMMON_SOURCES}
|
||||||
${COMMON_HEADERS}
|
${COMMON_HEADERS}
|
||||||
|
GUIConfig.cc
|
||||||
|
GUIConfig.h
|
||||||
GUIRenderer.cc
|
GUIRenderer.cc
|
||||||
GUIRenderer.h
|
GUIRenderer.h
|
||||||
GUIInputHandler.cc
|
GUIInputHandler.cc
|
||||||
@@ -151,6 +157,15 @@ if (${BUILD_GUI})
|
|||||||
|
|
||||||
# On macOS, build kge as a proper .app bundle
|
# On macOS, build kge as a proper .app bundle
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
|
# Define the icon file
|
||||||
|
set(MACOSX_BUNDLE_ICON_FILE kge.icns)
|
||||||
|
set(kge_ICON "${CMAKE_CURRENT_SOURCE_DIR}/${MACOSX_BUNDLE_ICON_FILE}")
|
||||||
|
|
||||||
|
# Add icon to the target sources and mark it as a resource
|
||||||
|
target_sources(kge PRIVATE ${kge_ICON})
|
||||||
|
set_source_files_properties(${kge_ICON} PROPERTIES
|
||||||
|
MACOSX_PACKAGE_LOCATION Resources)
|
||||||
|
|
||||||
# Configure Info.plist with version and identifiers
|
# Configure Info.plist with version and identifiers
|
||||||
set(KGE_BUNDLE_ID "dev.wntrmute.kge")
|
set(KGE_BUNDLE_ID "dev.wntrmute.kge")
|
||||||
configure_file(
|
configure_file(
|
||||||
@@ -162,6 +177,7 @@ if (${BUILD_GUI})
|
|||||||
MACOSX_BUNDLE TRUE
|
MACOSX_BUNDLE TRUE
|
||||||
MACOSX_BUNDLE_GUI_IDENTIFIER ${KGE_BUNDLE_ID}
|
MACOSX_BUNDLE_GUI_IDENTIFIER ${KGE_BUNDLE_ID}
|
||||||
MACOSX_BUNDLE_BUNDLE_NAME "kge"
|
MACOSX_BUNDLE_BUNDLE_NAME "kge"
|
||||||
|
MACOSX_BUNDLE_ICON_FILE ${MACOSX_BUNDLE_ICON_FILE}
|
||||||
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_BINARY_DIR}/kge-Info.plist")
|
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_BINARY_DIR}/kge-Info.plist")
|
||||||
|
|
||||||
install(TARGETS kge
|
install(TARGETS kge
|
||||||
|
|||||||
300
Command.cc
@@ -1,18 +1,18 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
#include "Command.h"
|
#include "Command.h"
|
||||||
#include "Editor.h"
|
#include "Editor.h"
|
||||||
#include "Buffer.h"
|
#include "Buffer.h"
|
||||||
#include "UndoSystem.h"
|
#include "UndoSystem.h"
|
||||||
// Note: Command layer must remain UI-agnostic. Do not include frontend/IO headers here.
|
|
||||||
|
|
||||||
|
|
||||||
// Keep buffer viewport offsets so that the cursor stays within the visible
|
// Keep buffer viewport offsets so that the cursor stays within the visible
|
||||||
// window based on the editor's current dimensions. The bottom row is reserved
|
// window based on the editor's current dimensions. The bottom row is reserved
|
||||||
// for the status line.
|
// for the status line.
|
||||||
static std::size_t
|
static std::size_t
|
||||||
compute_render_x(const std::string &line, std::size_t curx, std::size_t tabw)
|
compute_render_x(const std::string &line, const std::size_t curx, const std::size_t tabw)
|
||||||
{
|
{
|
||||||
std::size_t rx = 0;
|
std::size_t rx = 0;
|
||||||
for (std::size_t i = 0; i < curx && i < line.size(); ++i) {
|
for (std::size_t i = 0; i < curx && i < line.size(); ++i) {
|
||||||
@@ -453,6 +453,36 @@ cmd_save(CommandContext &ctx)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- Working directory commands ---
|
||||||
|
static bool
|
||||||
|
cmd_show_working_directory(CommandContext &ctx)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
std::filesystem::path cwd = std::filesystem::current_path();
|
||||||
|
ctx.editor.SetStatus(std::string("cwd: ") + cwd.string());
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
ctx.editor.SetStatus(std::string("cwd: <error> ") + e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool
|
||||||
|
cmd_change_working_directory_start(CommandContext &ctx)
|
||||||
|
{
|
||||||
|
std::string initial;
|
||||||
|
try {
|
||||||
|
initial = std::filesystem::current_path().string() + "/";
|
||||||
|
} catch (...) {
|
||||||
|
initial.clear();
|
||||||
|
}
|
||||||
|
ctx.editor.StartPrompt(Editor::PromptKind::Chdir, "chdir", initial);
|
||||||
|
ctx.editor.SetStatus(std::string("chdir: ") + ctx.editor.PromptText());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
cmd_save_as(CommandContext &ctx)
|
cmd_save_as(CommandContext &ctx)
|
||||||
{
|
{
|
||||||
@@ -646,6 +676,30 @@ cmd_open_file_start(CommandContext &ctx)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// GUI: toggle visual file picker (no-op in terminal; renderer will consume flag)
|
||||||
|
static bool
|
||||||
|
cmd_visual_file_picker_toggle(CommandContext &ctx)
|
||||||
|
{
|
||||||
|
// Toggle visibility
|
||||||
|
bool show = !ctx.editor.FilePickerVisible();
|
||||||
|
ctx.editor.SetFilePickerVisible(show);
|
||||||
|
if (show) {
|
||||||
|
// Initialize directory to current working directory if empty
|
||||||
|
if (ctx.editor.FilePickerDir().empty()) {
|
||||||
|
try {
|
||||||
|
ctx.editor.SetFilePickerDir(std::filesystem::current_path().string());
|
||||||
|
} catch (...) {
|
||||||
|
ctx.editor.SetFilePickerDir(".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.editor.SetStatus("Open File (visual)");
|
||||||
|
} else {
|
||||||
|
ctx.editor.SetStatus("Closed file picker");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
cmd_jump_to_line_start(CommandContext &ctx)
|
cmd_jump_to_line_start(CommandContext &ctx)
|
||||||
{
|
{
|
||||||
@@ -762,43 +816,141 @@ cmd_insert_text(CommandContext &ctx)
|
|||||||
}
|
}
|
||||||
// If a prompt is active, edit prompt text
|
// If a prompt is active, edit prompt text
|
||||||
if (ctx.editor.PromptActive()) {
|
if (ctx.editor.PromptActive()) {
|
||||||
// Special-case: buffer switch prompt supports Tab-completion
|
// Special-case: Tab-completion for prompts
|
||||||
if (ctx.editor.CurrentPromptKind() == Editor::PromptKind::BufferSwitch && ctx.arg == "\t") {
|
if (ctx.arg == "\t") {
|
||||||
// Complete against buffer names (path and basename)
|
auto kind = ctx.editor.CurrentPromptKind();
|
||||||
const std::string prefix = ctx.editor.PromptText();
|
// Buffer switch prompt supports Tab-completion on buffer names
|
||||||
std::vector<std::pair<std::string, std::size_t> > cands; // name, index
|
if (kind == Editor::PromptKind::BufferSwitch) {
|
||||||
const auto &bs = ctx.editor.Buffers();
|
// Complete against buffer names (path and basename)
|
||||||
for (std::size_t i = 0; i < bs.size(); ++i) {
|
const std::string prefix = ctx.editor.PromptText();
|
||||||
std::string full = buffer_display_name(bs[i]);
|
std::vector<std::pair<std::string, std::size_t> > cands; // name, index
|
||||||
std::string base = buffer_basename(bs[i]);
|
const auto &bs = ctx.editor.Buffers();
|
||||||
if (full.rfind(prefix, 0) == 0) {
|
for (std::size_t i = 0; i < bs.size(); ++i) {
|
||||||
cands.emplace_back(full, i);
|
std::string full = buffer_display_name(bs[i]);
|
||||||
|
std::string base = buffer_basename(bs[i]);
|
||||||
|
if (full.rfind(prefix, 0) == 0) {
|
||||||
|
cands.emplace_back(full, i);
|
||||||
|
}
|
||||||
|
if (base.rfind(prefix, 0) == 0 && base != full) {
|
||||||
|
cands.emplace_back(base, i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (base.rfind(prefix, 0) == 0 && base != full) {
|
if (cands.empty()) {
|
||||||
cands.emplace_back(base, i);
|
// no change
|
||||||
|
} else if (cands.size() == 1) {
|
||||||
|
ctx.editor.SetPromptText(cands[0].first);
|
||||||
|
} else {
|
||||||
|
// extend to longest common prefix
|
||||||
|
std::string lcp = cands[0].first;
|
||||||
|
for (std::size_t i = 1; i < cands.size(); ++i) {
|
||||||
|
const std::string &s = cands[i].first;
|
||||||
|
std::size_t j = 0;
|
||||||
|
while (j < lcp.size() && j < s.size() && lcp[j] == s[j])
|
||||||
|
++j;
|
||||||
|
lcp.resize(j);
|
||||||
|
if (lcp.empty())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!lcp.empty() && lcp != ctx.editor.PromptText())
|
||||||
|
ctx.editor.SetPromptText(lcp);
|
||||||
}
|
}
|
||||||
|
ctx.editor.SetStatus(ctx.editor.PromptLabel() + ": " + ctx.editor.PromptText());
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
if (cands.empty()) {
|
|
||||||
// no change
|
// File path completion for OpenFile/SaveAs/Chdir
|
||||||
} else if (cands.size() == 1) {
|
if (kind == Editor::PromptKind::OpenFile || kind == Editor::PromptKind::SaveAs
|
||||||
ctx.editor.SetPromptText(cands[0].first);
|
|| kind == Editor::PromptKind::Chdir) {
|
||||||
} else {
|
auto expand_user_path = [](const std::string &in) -> std::string {
|
||||||
// extend to longest common prefix
|
if (!in.empty() && in[0] == '~') {
|
||||||
std::string lcp = cands[0].first;
|
const char *home = std::getenv("HOME");
|
||||||
for (std::size_t i = 1; i < cands.size(); ++i) {
|
if (home && in.size() == 1)
|
||||||
const std::string &s = cands[i].first;
|
return std::string(home);
|
||||||
std::size_t j = 0;
|
if (home && (in.size() > 1) && (in[1] == '/' || in[1] == '\\')) {
|
||||||
while (j < lcp.size() && j < s.size() && lcp[j] == s[j])
|
std::string rest = in.substr(1); // keep leading slash
|
||||||
++j;
|
return std::string(home) + rest;
|
||||||
lcp.resize(j);
|
}
|
||||||
if (lcp.empty())
|
}
|
||||||
break;
|
return in;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string text = ctx.editor.PromptText();
|
||||||
|
// Build a path and split dir + base prefix
|
||||||
|
std::string expanded = expand_user_path(text);
|
||||||
|
std::filesystem::path p(expanded);
|
||||||
|
std::filesystem::path dir;
|
||||||
|
std::string base;
|
||||||
|
if (expanded.empty()) {
|
||||||
|
dir = std::filesystem::current_path();
|
||||||
|
base.clear();
|
||||||
|
} else if (std::filesystem::is_directory(p)) {
|
||||||
|
dir = p;
|
||||||
|
base.clear();
|
||||||
|
} else {
|
||||||
|
dir = p.parent_path();
|
||||||
|
base = p.filename().string();
|
||||||
|
if (dir.empty())
|
||||||
|
dir = std::filesystem::current_path();
|
||||||
}
|
}
|
||||||
if (!lcp.empty() && lcp != ctx.editor.PromptText())
|
|
||||||
ctx.editor.SetPromptText(lcp);
|
std::error_code ec;
|
||||||
|
std::vector<std::filesystem::directory_entry> entries;
|
||||||
|
std::filesystem::directory_iterator it(dir, ec), end;
|
||||||
|
for (; !ec && it != end; it.increment(ec)) {
|
||||||
|
entries.push_back(*it);
|
||||||
|
}
|
||||||
|
// Filter by base prefix
|
||||||
|
std::vector<std::string> cands;
|
||||||
|
for (const auto &de: entries) {
|
||||||
|
std::string name = de.path().filename().string();
|
||||||
|
if (base.empty() || name.rfind(base, 0) == 0) {
|
||||||
|
std::string candidate = (dir / name).string();
|
||||||
|
// For dirs, add trailing slash hint
|
||||||
|
if (de.is_directory(ec))
|
||||||
|
candidate += "/";
|
||||||
|
cands.push_back(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If no candidates, keep as-is
|
||||||
|
if (cands.empty()) {
|
||||||
|
// no-op
|
||||||
|
} else if (cands.size() == 1) {
|
||||||
|
ctx.editor.SetPromptText(cands[0]);
|
||||||
|
} else {
|
||||||
|
// Longest common prefix of display strings
|
||||||
|
auto lcp = cands[0];
|
||||||
|
for (size_t i = 1; i < cands.size(); ++i) {
|
||||||
|
const auto &s = cands[i];
|
||||||
|
size_t j = 0;
|
||||||
|
while (j < lcp.size() && j < s.size() && lcp[j] == s[j])
|
||||||
|
++j;
|
||||||
|
lcp.resize(j);
|
||||||
|
if (lcp.empty())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!lcp.empty() && lcp != ctx.editor.PromptText()) {
|
||||||
|
ctx.editor.SetPromptText(lcp);
|
||||||
|
} else {
|
||||||
|
// Show some choices in status (trim to avoid spam)
|
||||||
|
std::string msg = ctx.editor.PromptLabel() + ": ";
|
||||||
|
size_t shown = 0;
|
||||||
|
for (const auto &s: cands) {
|
||||||
|
if (shown >= 10) {
|
||||||
|
msg += " …";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (shown > 0)
|
||||||
|
msg += ' ';
|
||||||
|
msg += std::filesystem::path(s).filename().string();
|
||||||
|
++shown;
|
||||||
|
}
|
||||||
|
ctx.editor.SetStatus(msg);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.editor.SetStatus(ctx.editor.PromptLabel() + ": " + ctx.editor.PromptText());
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
ctx.editor.SetStatus(ctx.editor.PromptLabel() + ": " + ctx.editor.PromptText());
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.editor.AppendPromptText(ctx.arg);
|
ctx.editor.AppendPromptText(ctx.arg);
|
||||||
@@ -909,6 +1061,20 @@ cmd_newline(CommandContext &ctx)
|
|||||||
ensure_cursor_visible(ctx.editor, *b);
|
ensure_cursor_visible(ctx.editor, *b);
|
||||||
} else if (kind == Editor::PromptKind::OpenFile) {
|
} else if (kind == Editor::PromptKind::OpenFile) {
|
||||||
std::string err;
|
std::string err;
|
||||||
|
// Expand "~" to the user's home directory
|
||||||
|
auto expand_user_path = [](const std::string &in) -> std::string {
|
||||||
|
if (!in.empty() && in[0] == '~') {
|
||||||
|
const char *home = std::getenv("HOME");
|
||||||
|
if (home && in.size() == 1)
|
||||||
|
return std::string(home);
|
||||||
|
if (home && (in.size() > 1) && (in[1] == '/' || in[1] == '\\')) {
|
||||||
|
std::string rest = in.substr(1);
|
||||||
|
return std::string(home) + rest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return in;
|
||||||
|
};
|
||||||
|
value = expand_user_path(value);
|
||||||
if (value.empty()) {
|
if (value.empty()) {
|
||||||
ctx.editor.SetStatus("Open canceled (empty)");
|
ctx.editor.SetStatus("Open canceled (empty)");
|
||||||
} else if (!ctx.editor.OpenFile(value, err)) {
|
} else if (!ctx.editor.OpenFile(value, err)) {
|
||||||
@@ -953,6 +1119,21 @@ cmd_newline(CommandContext &ctx)
|
|||||||
if (!buf) {
|
if (!buf) {
|
||||||
ctx.editor.SetStatus("No buffer to save");
|
ctx.editor.SetStatus("No buffer to save");
|
||||||
} else {
|
} else {
|
||||||
|
// Expand "~" for save path
|
||||||
|
auto expand_user_path = [](const std::string &in) -> std::string {
|
||||||
|
if (!in.empty() && in[0] == '~') {
|
||||||
|
const char *home = std::getenv("HOME");
|
||||||
|
if (home && in.size() == 1)
|
||||||
|
return std::string(home);
|
||||||
|
if (home && (in.size() > 1) && (
|
||||||
|
in[1] == '/' || in[1] == '\\')) {
|
||||||
|
std::string rest = in.substr(1);
|
||||||
|
return std::string(home) + rest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return in;
|
||||||
|
};
|
||||||
|
value = expand_user_path(value);
|
||||||
// If this is a first-time save (unnamed/non-file-backed) and the
|
// If this is a first-time save (unnamed/non-file-backed) and the
|
||||||
// target exists, ask for confirmation before overwriting.
|
// target exists, ask for confirmation before overwriting.
|
||||||
if (!buf->IsFileBacked() && std::filesystem::exists(value)) {
|
if (!buf->IsFileBacked() && std::filesystem::exists(value)) {
|
||||||
@@ -1031,6 +1212,43 @@ cmd_newline(CommandContext &ctx)
|
|||||||
buf->SetCursor(0, y);
|
buf->SetCursor(0, y);
|
||||||
ensure_cursor_visible(ctx.editor, *buf);
|
ensure_cursor_visible(ctx.editor, *buf);
|
||||||
ctx.editor.SetStatus("Goto line " + std::to_string(line1));
|
ctx.editor.SetStatus("Goto line " + std::to_string(line1));
|
||||||
|
} else if (kind == Editor::PromptKind::Chdir) {
|
||||||
|
// Attempt to change the current working directory
|
||||||
|
if (value.empty()) {
|
||||||
|
ctx.editor.SetStatus("chdir canceled (empty)");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// Expand "~" for chdir
|
||||||
|
auto expand_user_path = [](const std::string &in) -> std::string {
|
||||||
|
if (!in.empty() && in[0] == '~') {
|
||||||
|
const char *home = std::getenv("HOME");
|
||||||
|
if (home && in.size() == 1)
|
||||||
|
return std::string(home);
|
||||||
|
if (home && (in.size() > 1) && (in[1] == '/' || in[1] == '\\')) {
|
||||||
|
std::string rest = in.substr(1);
|
||||||
|
return std::string(home) + rest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return in;
|
||||||
|
};
|
||||||
|
value = expand_user_path(value);
|
||||||
|
std::filesystem::path p(value);
|
||||||
|
std::error_code ec;
|
||||||
|
// Expand if value is relative: resolve against current_path implicitly
|
||||||
|
if (!std::filesystem::exists(p, ec)) {
|
||||||
|
ctx.editor.SetStatus(std::string("chdir: no such path: ") + value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!std::filesystem::is_directory(p, ec)) {
|
||||||
|
ctx.editor.SetStatus(std::string("chdir: not a directory: ") + value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
std::filesystem::current_path(p);
|
||||||
|
ctx.editor.SetStatus(std::string("cwd: ") + std::filesystem::current_path().string());
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
ctx.editor.SetStatus(std::string("chdir failed: ") + e.what());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2435,6 +2653,20 @@ InstallDefaultCommands()
|
|||||||
CommandId::MarkAllAndJumpEnd, "mark-all-jump-end", "Set mark at beginning and jump to end",
|
CommandId::MarkAllAndJumpEnd, "mark-all-jump-end", "Set mark at beginning and jump to end",
|
||||||
cmd_mark_all_and_jump_end
|
cmd_mark_all_and_jump_end
|
||||||
});
|
});
|
||||||
|
// GUI
|
||||||
|
CommandRegistry::Register({
|
||||||
|
CommandId::VisualFilePickerToggle, "file-picker-toggle", "Toggle visual file picker",
|
||||||
|
cmd_visual_file_picker_toggle
|
||||||
|
});
|
||||||
|
// Working directory
|
||||||
|
CommandRegistry::Register({
|
||||||
|
CommandId::ShowWorkingDirectory, "show-working-directory", "Show current working directory",
|
||||||
|
cmd_show_working_directory
|
||||||
|
});
|
||||||
|
CommandRegistry::Register({
|
||||||
|
CommandId::ChangeWorkingDirectory, "change-working-directory", "Change current working directory",
|
||||||
|
cmd_change_working_directory_start
|
||||||
|
});
|
||||||
// UI helpers
|
// UI helpers
|
||||||
CommandRegistry::Register(
|
CommandRegistry::Register(
|
||||||
{CommandId::UArgStatus, "uarg-status", "Update universal-arg status", cmd_uarg_status});
|
{CommandId::UArgStatus, "uarg-status", "Update universal-arg status", cmd_uarg_status});
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ enum class CommandId {
|
|||||||
KPrefix, // show "C-k _" prompt in status when entering k-command
|
KPrefix, // show "C-k _" prompt in status when entering k-command
|
||||||
FindStart, // begin incremental search (placeholder)
|
FindStart, // begin incremental search (placeholder)
|
||||||
OpenFileStart, // begin open-file prompt
|
OpenFileStart, // begin open-file prompt
|
||||||
|
// GUI: visual file picker
|
||||||
|
VisualFilePickerToggle,
|
||||||
// Buffers
|
// Buffers
|
||||||
BufferSwitchStart, // begin buffer switch prompt
|
BufferSwitchStart, // begin buffer switch prompt
|
||||||
BufferClose,
|
BufferClose,
|
||||||
@@ -74,6 +76,8 @@ enum class CommandId {
|
|||||||
MarkAllAndJumpEnd, // set mark at beginning, jump to end (C-k a)
|
MarkAllAndJumpEnd, // set mark at beginning, jump to end (C-k a)
|
||||||
// Direct navigation by line number
|
// Direct navigation by line number
|
||||||
JumpToLine, // prompt for line and jump (C-k g)
|
JumpToLine, // prompt for line and jump (C-k g)
|
||||||
|
ShowWorkingDirectory, // Display the current working directory in the editor message.
|
||||||
|
ChangeWorkingDirectory, // Change the editor's current directory.
|
||||||
// Meta
|
// Meta
|
||||||
UnknownKCommand, // arg: single character that was not recognized after C-k
|
UnknownKCommand, // arg: single character that was not recognized after C-k
|
||||||
};
|
};
|
||||||
|
|||||||
77
Editor.cc
@@ -1,7 +1,8 @@
|
|||||||
#include "Editor.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include "Editor.h"
|
||||||
|
|
||||||
|
|
||||||
Editor::Editor() = default;
|
Editor::Editor() = default;
|
||||||
@@ -43,6 +44,78 @@ Editor::CurrentBuffer() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static std::vector<std::filesystem::path>
|
||||||
|
split_reverse(const std::filesystem::path &p)
|
||||||
|
{
|
||||||
|
std::vector<std::filesystem::path> parts;
|
||||||
|
for (auto it = p; !it.empty(); it = it.parent_path()) {
|
||||||
|
if (it == it.parent_path()) {
|
||||||
|
// root or single element
|
||||||
|
if (!it.empty())
|
||||||
|
parts.push_back(it);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
parts.push_back(it.filename());
|
||||||
|
}
|
||||||
|
return parts; // from leaf toward root
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string
|
||||||
|
Editor::DisplayNameFor(const Buffer &buf) const
|
||||||
|
{
|
||||||
|
std::string full = buf.Filename();
|
||||||
|
if (full.empty())
|
||||||
|
return std::string("[no name]");
|
||||||
|
|
||||||
|
std::filesystem::path target(full);
|
||||||
|
auto target_parts = split_reverse(target);
|
||||||
|
if (target_parts.empty())
|
||||||
|
return target.filename().string();
|
||||||
|
|
||||||
|
// Prepare list of other buffer paths
|
||||||
|
std::vector<std::vector<std::filesystem::path> > others;
|
||||||
|
others.reserve(buffers_.size());
|
||||||
|
for (const auto &b: buffers_) {
|
||||||
|
if (&b == &buf)
|
||||||
|
continue;
|
||||||
|
if (b.Filename().empty())
|
||||||
|
continue;
|
||||||
|
others.push_back(split_reverse(std::filesystem::path(b.Filename())));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increase suffix length until unique among others
|
||||||
|
std::size_t need = 1; // at least basename
|
||||||
|
for (;;) {
|
||||||
|
// Build candidate suffix for target
|
||||||
|
std::filesystem::path cand;
|
||||||
|
for (std::size_t i = 0; i < need && i < target_parts.size(); ++i) {
|
||||||
|
cand = std::filesystem::path(target_parts[i]) / cand;
|
||||||
|
}
|
||||||
|
// Compare against others
|
||||||
|
bool clash = false;
|
||||||
|
for (const auto &o_parts: others) {
|
||||||
|
std::filesystem::path ocand;
|
||||||
|
for (std::size_t i = 0; i < need && i < o_parts.size(); ++i) {
|
||||||
|
ocand = std::filesystem::path(o_parts[i]) / ocand;
|
||||||
|
}
|
||||||
|
if (ocand == cand) {
|
||||||
|
clash = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!clash || need >= target_parts.size()) {
|
||||||
|
std::string s = cand.string();
|
||||||
|
// Remove any trailing slash that may appear from root joining
|
||||||
|
if (!s.empty() && (s.back() == '/' || s.back() == '\\'))
|
||||||
|
s.pop_back();
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
++need;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
std::size_t
|
std::size_t
|
||||||
Editor::AddBuffer(const Buffer &buf)
|
Editor::AddBuffer(const Buffer &buf)
|
||||||
{
|
{
|
||||||
|
|||||||
36
Editor.h
@@ -302,7 +302,7 @@ public:
|
|||||||
|
|
||||||
|
|
||||||
// --- Generic Prompt subsystem (for search, open-file, save-as, etc.) ---
|
// --- Generic Prompt subsystem (for search, open-file, save-as, etc.) ---
|
||||||
enum class PromptKind { None = 0, Search, OpenFile, SaveAs, Confirm, BufferSwitch, GotoLine };
|
enum class PromptKind { None = 0, Search, OpenFile, SaveAs, Confirm, BufferSwitch, GotoLine, Chdir };
|
||||||
|
|
||||||
|
|
||||||
void StartPrompt(PromptKind kind, const std::string &label, const std::string &initial)
|
void StartPrompt(PromptKind kind, const std::string &label, const std::string &initial)
|
||||||
@@ -409,6 +409,11 @@ public:
|
|||||||
|
|
||||||
const Buffer *CurrentBuffer() const;
|
const Buffer *CurrentBuffer() const;
|
||||||
|
|
||||||
|
// Compute a display-friendly short name for a buffer path that is the
|
||||||
|
// shortest unique suffix among all open buffers. If buffer has no name,
|
||||||
|
// returns "[no name]".
|
||||||
|
[[nodiscard]] std::string DisplayNameFor(const Buffer &buf) const;
|
||||||
|
|
||||||
// Add an existing buffer (copy/move) or open from file path
|
// Add an existing buffer (copy/move) or open from file path
|
||||||
std::size_t AddBuffer(const Buffer &buf);
|
std::size_t AddBuffer(const Buffer &buf);
|
||||||
|
|
||||||
@@ -436,6 +441,31 @@ public:
|
|||||||
return buffers_;
|
return buffers_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- GUI: Visual File Picker state ---
|
||||||
|
void SetFilePickerVisible(bool on)
|
||||||
|
{
|
||||||
|
file_picker_visible_ = on;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[[nodiscard]] bool FilePickerVisible() const
|
||||||
|
{
|
||||||
|
return file_picker_visible_;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SetFilePickerDir(const std::string &path)
|
||||||
|
{
|
||||||
|
file_picker_dir_ = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[[nodiscard]] const std::string &FilePickerDir() const
|
||||||
|
{
|
||||||
|
return file_picker_dir_;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::size_t rows_ = 0, cols_ = 0;
|
std::size_t rows_ = 0, cols_ = 0;
|
||||||
int mode_ = 0;
|
int mode_ = 0;
|
||||||
@@ -473,6 +503,10 @@ private:
|
|||||||
std::string prompt_label_;
|
std::string prompt_label_;
|
||||||
std::string prompt_text_;
|
std::string prompt_text_;
|
||||||
std::string pending_overwrite_path_;
|
std::string pending_overwrite_path_;
|
||||||
|
|
||||||
|
// GUI-only state (safe no-op in terminal builds)
|
||||||
|
bool file_picker_visible_ = false;
|
||||||
|
std::string file_picker_dir_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KTE_EDITOR_H
|
#endif // KTE_EDITOR_H
|
||||||
|
|||||||
@@ -4,8 +4,6 @@
|
|||||||
#ifndef KTE_FRONTEND_H
|
#ifndef KTE_FRONTEND_H
|
||||||
#define KTE_FRONTEND_H
|
#define KTE_FRONTEND_H
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
|
|
||||||
class Editor;
|
class Editor;
|
||||||
class InputHandler;
|
class InputHandler;
|
||||||
|
|||||||
107
GUIConfig.cc
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "GUIConfig.h"
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
trim(std::string &s)
|
||||||
|
{
|
||||||
|
auto not_space = [](int ch) {
|
||||||
|
return !std::isspace(ch);
|
||||||
|
};
|
||||||
|
s.erase(s.begin(), std::find_if(s.begin(), s.end(), not_space));
|
||||||
|
s.erase(std::find_if(s.rbegin(), s.rend(), not_space).base(), s.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static std::string
|
||||||
|
default_config_path()
|
||||||
|
{
|
||||||
|
const char *home = std::getenv("HOME");
|
||||||
|
if (!home || !*home)
|
||||||
|
return std::string();
|
||||||
|
std::string path(home);
|
||||||
|
path += "/.config/kte/kge.ini";
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GUIConfig
|
||||||
|
GUIConfig::Load()
|
||||||
|
{
|
||||||
|
GUIConfig cfg; // defaults already set
|
||||||
|
std::string path = default_config_path();
|
||||||
|
if (!path.empty()) {
|
||||||
|
cfg.LoadFromFile(path);
|
||||||
|
}
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
GUIConfig::LoadFromFile(const std::string &path)
|
||||||
|
{
|
||||||
|
std::ifstream in(path);
|
||||||
|
if (!in.good())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(in, line)) {
|
||||||
|
// Remove comments starting with '#' or ';'
|
||||||
|
auto hash = line.find('#');
|
||||||
|
if (hash != std::string::npos)
|
||||||
|
line.erase(hash);
|
||||||
|
auto sc = line.find("//");
|
||||||
|
if (sc != std::string::npos)
|
||||||
|
line.erase(sc);
|
||||||
|
// Basic key=value
|
||||||
|
auto eq = line.find('=');
|
||||||
|
if (eq == std::string::npos)
|
||||||
|
continue;
|
||||||
|
std::string key = line.substr(0, eq);
|
||||||
|
std::string val = line.substr(eq + 1);
|
||||||
|
trim(key);
|
||||||
|
trim(val);
|
||||||
|
// Strip trailing semicolon
|
||||||
|
if (!val.empty() && val.back() == ';') {
|
||||||
|
val.pop_back();
|
||||||
|
trim(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lowercase key
|
||||||
|
std::transform(key.begin(), key.end(), key.begin(), [](unsigned char c) {
|
||||||
|
return (char) std::tolower(c);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (key == "fullscreen") {
|
||||||
|
fullscreen = (val == "1" || val == "true" || val == "on" || val == "yes");
|
||||||
|
} else if (key == "columns" || key == "cols") {
|
||||||
|
int v = columns;
|
||||||
|
try {
|
||||||
|
v = std::stoi(val);
|
||||||
|
} catch (...) {}
|
||||||
|
if (v > 0)
|
||||||
|
columns = v;
|
||||||
|
} else if (key == "rows") {
|
||||||
|
int v = rows;
|
||||||
|
try {
|
||||||
|
v = std::stoi(val);
|
||||||
|
} catch (...) {}
|
||||||
|
if (v > 0)
|
||||||
|
rows = v;
|
||||||
|
} else if (key == "font_size" || key == "fontsize") {
|
||||||
|
float v = font_size;
|
||||||
|
try {
|
||||||
|
v = std::stof(val);
|
||||||
|
} catch (...) {}
|
||||||
|
if (v > 0.0f)
|
||||||
|
font_size = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
27
GUIConfig.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* GUIConfig - loads simple GUI configuration from $HOME/.config/kte/kge.ini
|
||||||
|
*/
|
||||||
|
#ifndef KTE_GUI_CONFIG_H
|
||||||
|
#define KTE_GUI_CONFIG_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#ifndef KTE_FONT_SIZE
|
||||||
|
#define KTE_FONT_SIZE 16.0f
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class GUIConfig {
|
||||||
|
public:
|
||||||
|
bool fullscreen = false;
|
||||||
|
int columns = 80;
|
||||||
|
int rows = 42;
|
||||||
|
float font_size = (float) KTE_FONT_SIZE;
|
||||||
|
|
||||||
|
// Load from default path: $HOME/.config/kte/kge.ini
|
||||||
|
static GUIConfig Load();
|
||||||
|
|
||||||
|
// Load from explicit path. Returns true if file existed and was parsed.
|
||||||
|
bool LoadFromFile(const std::string &path);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // KTE_GUI_CONFIG_H
|
||||||
@@ -1,18 +1,25 @@
|
|||||||
#include <SDL.h>
|
|
||||||
#include <SDL_opengl.h>
|
|
||||||
|
|
||||||
#include <imgui.h>
|
|
||||||
#include <backends/imgui_impl_sdl2.h>
|
|
||||||
#include <backends/imgui_impl_opengl3.h>
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <SDL_opengl.h>
|
||||||
|
#include <imgui.h>
|
||||||
|
#include <backends/imgui_impl_sdl2.h>
|
||||||
|
#include <backends/imgui_impl_opengl3.h>
|
||||||
|
|
||||||
#include "Editor.h"
|
#include "Editor.h"
|
||||||
#include "Command.h"
|
#include "Command.h"
|
||||||
#include "GUIFrontend.h"
|
#include "GUIFrontend.h"
|
||||||
#include "Font.h" // embedded default font (DefaultFontRegular)
|
#include "Font.h" // embedded default font (DefaultFontRegular)
|
||||||
|
#include "GUIConfig.h"
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef KTE_FONT_SIZE
|
||||||
|
#define KTE_FONT_SIZE 16.0f
|
||||||
|
#endif
|
||||||
|
|
||||||
static const char *kGlslVersion = "#version 150"; // GL 3.2 core (macOS compatible)
|
static const char *kGlslVersion = "#version 150"; // GL 3.2 core (macOS compatible)
|
||||||
|
|
||||||
@@ -24,6 +31,9 @@ GUIFrontend::Init(Editor &ed)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load GUI configuration (fullscreen, columns/rows, font size)
|
||||||
|
const auto [fullscreen, columns, rows, font_size] = GUIConfig::Load();
|
||||||
|
|
||||||
// GL attributes for core profile
|
// GL attributes for core profile
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||||
@@ -33,14 +43,56 @@ GUIFrontend::Init(Editor &ed)
|
|||||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||||
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
||||||
|
|
||||||
|
// Compute desired window size from config
|
||||||
|
Uint32 win_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
|
||||||
|
|
||||||
|
if (fullscreen) {
|
||||||
|
// "Fullscreen": fill the usable bounds of the primary display.
|
||||||
|
// On macOS, do NOT use true fullscreen so the menu/status bar remains visible.
|
||||||
|
SDL_Rect usable{};
|
||||||
|
if (SDL_GetDisplayUsableBounds(0, &usable) == 0) {
|
||||||
|
width_ = usable.w;
|
||||||
|
height_ = usable.h;
|
||||||
|
}
|
||||||
|
#if !defined(__APPLE__)
|
||||||
|
// Non-macOS: desktop fullscreen uses the current display resolution.
|
||||||
|
win_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
// Windowed: width = columns * font_size, height = (rows * 2) * font_size
|
||||||
|
int w = static_cast<int>(columns * font_size);
|
||||||
|
int h = static_cast<int>((rows * 2) * font_size);
|
||||||
|
|
||||||
|
// As a safety, clamp to display usable bounds if retrievable
|
||||||
|
SDL_Rect usable{};
|
||||||
|
if (SDL_GetDisplayUsableBounds(0, &usable) == 0) {
|
||||||
|
w = std::min(w, usable.w);
|
||||||
|
h = std::min(h, usable.h);
|
||||||
|
}
|
||||||
|
width_ = std::max(320, w);
|
||||||
|
height_ = std::max(200, h);
|
||||||
|
}
|
||||||
|
|
||||||
window_ = SDL_CreateWindow(
|
window_ = SDL_CreateWindow(
|
||||||
"kte",
|
"kge - kyle's text editor " KTE_VERSION_STR,
|
||||||
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||||
width_, height_,
|
width_, height_,
|
||||||
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
win_flags);
|
||||||
if (!window_)
|
if (!window_)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
// macOS: when "fullscreen" is requested, position the window at the
|
||||||
|
// top-left of the usable display area to mimic fullscreen while keeping
|
||||||
|
// the system menu bar visible.
|
||||||
|
if (fullscreen) {
|
||||||
|
SDL_Rect usable{};
|
||||||
|
if (SDL_GetDisplayUsableBounds(0, &usable) == 0) {
|
||||||
|
SDL_SetWindowPosition(window_, usable.x, usable.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
gl_ctx_ = SDL_GL_CreateContext(window_);
|
gl_ctx_ = SDL_GL_CreateContext(window_);
|
||||||
if (!gl_ctx_)
|
if (!gl_ctx_)
|
||||||
return false;
|
return false;
|
||||||
@@ -64,11 +116,23 @@ GUIFrontend::Init(Editor &ed)
|
|||||||
width_ = w;
|
width_ = w;
|
||||||
height_ = h;
|
height_ = h;
|
||||||
|
|
||||||
// Initialize GUI font from embedded default
|
#if defined(__APPLE__)
|
||||||
#ifndef KTE_FONT_SIZE
|
// Workaround: On macOS Retina when starting maximized, we sometimes get a
|
||||||
#define KTE_FONT_SIZE 16.0f
|
// subtle input vs draw alignment mismatch until the first manual resize.
|
||||||
|
// Nudge the window size by 1px and back to trigger a proper internal
|
||||||
|
// recomputation, without visible impact.
|
||||||
|
if (w > 1 && h > 1) {
|
||||||
|
SDL_SetWindowSize(window_, w - 1, h - 1);
|
||||||
|
SDL_SetWindowSize(window_, w, h);
|
||||||
|
// Update cached size in case backend reports immediately
|
||||||
|
SDL_GetWindowSize(window_, &w, &h);
|
||||||
|
width_ = w;
|
||||||
|
height_ = h;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
LoadGuiFont_(nullptr, (float) KTE_FONT_SIZE);
|
|
||||||
|
// Initialize GUI font from embedded default (use configured size or compiled default)
|
||||||
|
LoadGuiFont_(nullptr, (float) font_size);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "GUIInputHandler.h"
|
#include "GUIInputHandler.h"
|
||||||
#include "GUIRenderer.h"
|
#include "GUIRenderer.h"
|
||||||
|
|
||||||
|
|
||||||
struct SDL_Window;
|
struct SDL_Window;
|
||||||
typedef void *SDL_GLContext;
|
typedef void *SDL_GLContext;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
#include <SDL.h>
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <ncurses.h>
|
#include <ncurses.h>
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
#include "GUIInputHandler.h"
|
#include "GUIInputHandler.h"
|
||||||
#include "KKeymap.h"
|
#include "KKeymap.h"
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,12 @@
|
|||||||
#ifndef KTE_GUI_INPUT_HANDLER_H
|
#ifndef KTE_GUI_INPUT_HANDLER_H
|
||||||
#define KTE_GUI_INPUT_HANDLER_H
|
#define KTE_GUI_INPUT_HANDLER_H
|
||||||
|
|
||||||
#include <queue>
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
#include "InputHandler.h"
|
#include "InputHandler.h"
|
||||||
|
|
||||||
|
|
||||||
union SDL_Event; // fwd decl to avoid including SDL here (SDL defines SDL_Event as a union)
|
union SDL_Event; // fwd decl to avoid including SDL here (SDL defines SDL_Event as a union)
|
||||||
|
|
||||||
class GUIInputHandler final : public InputHandler {
|
class GUIInputHandler final : public InputHandler {
|
||||||
|
|||||||
395
GUIRenderer.cc
@@ -1,27 +1,44 @@
|
|||||||
#include "GUIRenderer.h"
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
#include "Editor.h"
|
#include <filesystem>
|
||||||
#include "Buffer.h"
|
#include <limits>
|
||||||
#include "Command.h"
|
#include <string>
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <cstdio>
|
|
||||||
#include <string>
|
#include "GUIRenderer.h"
|
||||||
#include <filesystem>
|
#include "Buffer.h"
|
||||||
|
#include "Command.h"
|
||||||
|
#include "Editor.h"
|
||||||
|
|
||||||
|
|
||||||
// Version string expected to be provided by build system as KTE_VERSION_STR
|
// Version string expected to be provided by build system as KTE_VERSION_STR
|
||||||
#ifndef KTE_VERSION_STR
|
#ifndef KTE_VERSION_STR
|
||||||
# define KTE_VERSION_STR "dev"
|
# define KTE_VERSION_STR "dev"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// ImGui compatibility: some bundled ImGui versions (or builds without docking)
|
||||||
|
// don't define ImGuiWindowFlags_NoDocking. Treat it as 0 in that case.
|
||||||
|
#ifndef ImGuiWindowFlags_NoDocking
|
||||||
|
# define ImGuiWindowFlags_NoDocking 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
GUIRenderer::Draw(Editor &ed)
|
GUIRenderer::Draw(Editor &ed)
|
||||||
{
|
{
|
||||||
// Make the editor window occupy the entire GUI container/viewport
|
// Make the editor window occupy the entire GUI container/viewport
|
||||||
ImGuiViewport *vp = ImGui::GetMainViewport();
|
ImGuiViewport *vp = ImGui::GetMainViewport();
|
||||||
ImGui::SetNextWindowPos(vp->Pos);
|
// On HiDPI/Retina, snap to integer pixels to prevent any draw vs hit-test
|
||||||
ImGui::SetNextWindowSize(vp->Size);
|
// mismatches that can appear on the very first maximized frame.
|
||||||
|
ImVec2 main_pos = vp->Pos;
|
||||||
|
ImVec2 main_sz = vp->Size;
|
||||||
|
main_pos.x = std::floor(main_pos.x + 0.5f);
|
||||||
|
main_pos.y = std::floor(main_pos.y + 0.5f);
|
||||||
|
main_sz.x = std::floor(main_sz.x + 0.5f);
|
||||||
|
main_sz.y = std::floor(main_sz.y + 0.5f);
|
||||||
|
ImGui::SetNextWindowPos(main_pos);
|
||||||
|
ImGui::SetNextWindowSize(main_sz);
|
||||||
|
|
||||||
ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar
|
ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar
|
||||||
| ImGuiWindowFlags_NoResize
|
| ImGuiWindowFlags_NoResize
|
||||||
@@ -63,30 +80,45 @@ GUIRenderer::Draw(Editor &ed)
|
|||||||
bool forced_scroll = false;
|
bool forced_scroll = false;
|
||||||
{
|
{
|
||||||
static long prev_buf_rowoffs = -1; // previous frame's Buffer::Rowoffs
|
static long prev_buf_rowoffs = -1; // previous frame's Buffer::Rowoffs
|
||||||
|
static long prev_buf_coloffs = -1; // previous frame's Buffer::Coloffs
|
||||||
static float prev_scroll_y = -1.0f; // previous frame's ImGui scroll Y in pixels
|
static float prev_scroll_y = -1.0f; // previous frame's ImGui scroll Y in pixels
|
||||||
|
static float prev_scroll_x = -1.0f; // previous frame's ImGui scroll X in pixels
|
||||||
|
|
||||||
const long buf_rowoffs = static_cast<long>(buf->Rowoffs());
|
const long buf_rowoffs = static_cast<long>(buf->Rowoffs());
|
||||||
|
const long buf_coloffs = static_cast<long>(buf->Coloffs());
|
||||||
const long scroll_top = static_cast<long>(scroll_y / row_h);
|
const long scroll_top = static_cast<long>(scroll_y / row_h);
|
||||||
|
const long scroll_left = static_cast<long>(scroll_x / space_w);
|
||||||
|
|
||||||
// Detect programmatic change (e.g., keyboard navigation ensured visibility)
|
// Detect programmatic change (e.g., keyboard navigation ensured visibility)
|
||||||
if (prev_buf_rowoffs >= 0 && buf_rowoffs != prev_buf_rowoffs) {
|
if (prev_buf_rowoffs >= 0 && buf_rowoffs != prev_buf_rowoffs) {
|
||||||
ImGui::SetScrollY(static_cast<float>(buf_rowoffs) * row_h);
|
ImGui::SetScrollY(static_cast<float>(buf_rowoffs) * row_h);
|
||||||
scroll_y = ImGui::GetScrollY();
|
scroll_y = ImGui::GetScrollY();
|
||||||
forced_scroll = true;
|
forced_scroll = true;
|
||||||
} else {
|
}
|
||||||
// If user scrolled (scroll_y changed), update buffer row offset accordingly
|
if (prev_buf_coloffs >= 0 && buf_coloffs != prev_buf_coloffs) {
|
||||||
if (prev_scroll_y >= 0.0f && scroll_y != prev_scroll_y) {
|
ImGui::SetScrollX(static_cast<float>(buf_coloffs) * space_w);
|
||||||
if (Buffer *mbuf = const_cast<Buffer *>(buf)) {
|
scroll_x = ImGui::GetScrollX();
|
||||||
// Keep horizontal offset owned by GUI; only update vertical offset here
|
forced_scroll = true;
|
||||||
mbuf->SetOffsets(static_cast<std::size_t>(std::max(0L, scroll_top)),
|
}
|
||||||
mbuf->Coloffs());
|
// If user scrolled, update buffer offsets accordingly
|
||||||
}
|
if (prev_scroll_y >= 0.0f && scroll_y != prev_scroll_y) {
|
||||||
|
if (Buffer *mbuf = const_cast<Buffer *>(buf)) {
|
||||||
|
mbuf->SetOffsets(static_cast<std::size_t>(std::max(0L, scroll_top)),
|
||||||
|
mbuf->Coloffs());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (prev_scroll_x >= 0.0f && scroll_x != prev_scroll_x) {
|
||||||
|
if (Buffer *mbuf = const_cast<Buffer *>(buf)) {
|
||||||
|
mbuf->SetOffsets(mbuf->Rowoffs(),
|
||||||
|
static_cast<std::size_t>(std::max(0L, scroll_left)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update trackers for next frame
|
// Update trackers for next frame
|
||||||
prev_buf_rowoffs = static_cast<long>(buf->Rowoffs());
|
prev_buf_rowoffs = static_cast<long>(buf->Rowoffs());
|
||||||
|
prev_buf_coloffs = static_cast<long>(buf->Coloffs());
|
||||||
prev_scroll_y = ImGui::GetScrollY();
|
prev_scroll_y = ImGui::GetScrollY();
|
||||||
|
prev_scroll_x = ImGui::GetScrollX();
|
||||||
}
|
}
|
||||||
// Synchronize cursor and scrolling.
|
// Synchronize cursor and scrolling.
|
||||||
// Ensure the cursor is visible even on the first frame or when it didn't move,
|
// Ensure the cursor is visible even on the first frame or when it didn't move,
|
||||||
@@ -120,61 +152,127 @@ GUIRenderer::Draw(Editor &ed)
|
|||||||
// Handle mouse click before rendering to avoid dependent on drawn items
|
// Handle mouse click before rendering to avoid dependent on drawn items
|
||||||
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||||
ImVec2 mp = ImGui::GetIO().MousePos;
|
ImVec2 mp = ImGui::GetIO().MousePos;
|
||||||
// Map Y to row
|
// Compute viewport-relative row so (0) is top row of the visible area
|
||||||
float rel_y = scroll_y + (mp.y - list_origin.y);
|
float vy_f = (mp.y - list_origin.y - scroll_y) / row_h;
|
||||||
long row = static_cast<long>(rel_y / row_h);
|
long vy = static_cast<long>(vy_f);
|
||||||
if (row < 0)
|
if (vy < 0)
|
||||||
row = 0;
|
vy = 0;
|
||||||
if (row >= static_cast<long>(lines.size()))
|
|
||||||
row = static_cast<long>(lines.empty() ? 0 : (lines.size() - 1));
|
// Clamp vy within visible content height to avoid huge jumps
|
||||||
// Map X to column by measuring text width
|
ImVec2 cr_min = ImGui::GetWindowContentRegionMin();
|
||||||
std::size_t col = 0;
|
ImVec2 cr_max = ImGui::GetWindowContentRegionMax();
|
||||||
if (!lines.empty()) {
|
float child_h = (cr_max.y - cr_min.y);
|
||||||
const std::string &line = lines[static_cast<std::size_t>(row)];
|
long vis_rows = static_cast<long>(child_h / row_h);
|
||||||
float rel_x = scroll_x + (mp.x - list_origin.x);
|
if (vis_rows < 1)
|
||||||
if (rel_x <= 0.0f) {
|
vis_rows = 1;
|
||||||
col = 0;
|
if (vy >= vis_rows)
|
||||||
} else {
|
vy = vis_rows - 1;
|
||||||
float prev_w = 0.0f;
|
|
||||||
for (std::size_t i = 1; i <= line.size(); ++i) {
|
// Translate viewport row to buffer row using Buffer::Rowoffs
|
||||||
ImVec2 sz = ImGui::CalcTextSize(
|
std::size_t by = buf->Rowoffs() + static_cast<std::size_t>(vy);
|
||||||
line.c_str(), line.c_str() + static_cast<long>(i));
|
if (by >= lines.size()) {
|
||||||
if (sz.x >= rel_x) {
|
if (!lines.empty())
|
||||||
// Pick closer between i-1 and i
|
by = lines.size() - 1;
|
||||||
float d_prev = rel_x - prev_w;
|
else
|
||||||
float d_curr = sz.x - rel_x;
|
by = 0;
|
||||||
col = (d_prev <= d_curr) ? (i - 1) : i;
|
}
|
||||||
break;
|
|
||||||
}
|
// Compute desired pixel X inside the viewport content (subtract horizontal scroll)
|
||||||
prev_w = sz.x;
|
float px = (mp.x - list_origin.x - scroll_x);
|
||||||
if (i == line.size()) {
|
if (px < 0.0f)
|
||||||
// clicked beyond EOL
|
px = 0.0f;
|
||||||
float eol_w = sz.x;
|
|
||||||
col = (rel_x > eol_w + space_w * 0.5f)
|
// Convert pixel X to a render-column target including horizontal col offset
|
||||||
? line.size()
|
// Use our own tab expansion of width 8 to match command layer logic.
|
||||||
: line.size();
|
const std::string &line_clicked = lines[by];
|
||||||
}
|
const std::size_t tabw = 8;
|
||||||
|
// We iterate source columns computing absolute rendered column (rx_abs) from 0,
|
||||||
|
// then translate to viewport-space by subtracting Coloffs.
|
||||||
|
std::size_t coloffs = buf->Coloffs();
|
||||||
|
std::size_t rx_abs = 0; // absolute rendered column
|
||||||
|
std::size_t i = 0; // source column iterator
|
||||||
|
|
||||||
|
// Fast-forward i until rx_abs >= coloffs to align with leftmost visible column
|
||||||
|
if (!line_clicked.empty() && coloffs > 0) {
|
||||||
|
while (i < line_clicked.size() && rx_abs < coloffs) {
|
||||||
|
if (line_clicked[i] == '\t') {
|
||||||
|
rx_abs += (tabw - (rx_abs % tabw));
|
||||||
|
} else {
|
||||||
|
rx_abs += 1;
|
||||||
}
|
}
|
||||||
|
++i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Dispatch command to move cursor
|
|
||||||
|
// Now search for closest source column to clicked px within/after viewport
|
||||||
|
std::size_t best_col = i; // default to first visible column
|
||||||
|
float best_dist = std::numeric_limits<float>::infinity();
|
||||||
|
while (true) {
|
||||||
|
// For i in [current..size], evaluate candidate including the implicit end position
|
||||||
|
std::size_t rx_view = (rx_abs >= coloffs) ? (rx_abs - coloffs) : 0;
|
||||||
|
float rx_px = static_cast<float>(rx_view) * space_w;
|
||||||
|
float dist = std::fabs(px - rx_px);
|
||||||
|
if (dist <= best_dist) {
|
||||||
|
best_dist = dist;
|
||||||
|
best_col = i;
|
||||||
|
}
|
||||||
|
if (i == line_clicked.size())
|
||||||
|
break;
|
||||||
|
// advance to next source column
|
||||||
|
if (line_clicked[i] == '\t') {
|
||||||
|
rx_abs += (tabw - (rx_abs % tabw));
|
||||||
|
} else {
|
||||||
|
rx_abs += 1;
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch absolute buffer coordinates (row:col)
|
||||||
char tmp[64];
|
char tmp[64];
|
||||||
std::snprintf(tmp, sizeof(tmp), "%ld:%zu", row, col);
|
std::snprintf(tmp, sizeof(tmp), "%zu:%zu", by, best_col);
|
||||||
Execute(ed, CommandId::MoveCursorTo, std::string(tmp));
|
Execute(ed, CommandId::MoveCursorTo, std::string(tmp));
|
||||||
}
|
}
|
||||||
|
// Cache current horizontal offset in rendered columns
|
||||||
|
const std::size_t coloffs_now = buf->Coloffs();
|
||||||
for (std::size_t i = rowoffs; i < lines.size(); ++i) {
|
for (std::size_t i = rowoffs; i < lines.size(); ++i) {
|
||||||
// Capture the screen position before drawing the line
|
// Capture the screen position before drawing the line
|
||||||
ImVec2 line_pos = ImGui::GetCursorScreenPos();
|
ImVec2 line_pos = ImGui::GetCursorScreenPos();
|
||||||
const std::string &line = lines[i];
|
const std::string &line = lines[i];
|
||||||
ImGui::TextUnformatted(line.c_str());
|
|
||||||
|
// Expand tabs to spaces with width=8 and apply horizontal scroll offset
|
||||||
|
const std::size_t tabw = 8;
|
||||||
|
std::string expanded;
|
||||||
|
expanded.reserve(line.size() + 16);
|
||||||
|
std::size_t rx_abs_draw = 0; // rendered column for drawing
|
||||||
|
// Emit entire line (ImGui child scrolling will handle clipping)
|
||||||
|
for (std::size_t src = 0; src < line.size(); ++src) {
|
||||||
|
char c = line[src];
|
||||||
|
if (c == '\t') {
|
||||||
|
std::size_t adv = (tabw - (rx_abs_draw % tabw));
|
||||||
|
// Emit spaces for the tab
|
||||||
|
expanded.append(adv, ' ');
|
||||||
|
rx_abs_draw += adv;
|
||||||
|
} else {
|
||||||
|
expanded.push_back(c);
|
||||||
|
rx_abs_draw += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TextUnformatted(expanded.c_str());
|
||||||
|
|
||||||
// Draw a visible cursor indicator on the current line
|
// Draw a visible cursor indicator on the current line
|
||||||
if (i == cy) {
|
if (i == cy) {
|
||||||
// Compute X offset by measuring text width up to cursor column
|
// Compute rendered X (rx) from source column with tab expansion
|
||||||
std::size_t px_count = std::min(cx, line.size());
|
std::size_t rx_abs = 0;
|
||||||
ImVec2 pre_sz = ImGui::CalcTextSize(line.c_str(),
|
for (std::size_t k = 0; k < std::min(cx, line.size()); ++k) {
|
||||||
line.c_str() + static_cast<long>(px_count));
|
if (line[k] == '\t')
|
||||||
ImVec2 p0 = ImVec2(line_pos.x + pre_sz.x, line_pos.y);
|
rx_abs += (tabw - (rx_abs % tabw));
|
||||||
|
else
|
||||||
|
rx_abs += 1;
|
||||||
|
}
|
||||||
|
// Convert to viewport x by subtracting horizontal col offset
|
||||||
|
std::size_t rx_viewport = (rx_abs > coloffs_now) ? (rx_abs - coloffs_now) : 0;
|
||||||
|
ImVec2 p0 = ImVec2(line_pos.x + static_cast<float>(rx_viewport) * space_w, line_pos.y);
|
||||||
ImVec2 p1 = ImVec2(p0.x + space_w, p0.y + line_h);
|
ImVec2 p1 = ImVec2(p0.x + space_w, p0.y + line_h);
|
||||||
ImU32 col = IM_COL32(200, 200, 255, 128); // soft highlight
|
ImU32 col = IM_COL32(200, 200, 255, 128); // soft highlight
|
||||||
ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, col);
|
ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, col);
|
||||||
@@ -204,18 +302,38 @@ GUIRenderer::Draw(Editor &ed)
|
|||||||
left += "kge"; // GUI app name
|
left += "kge"; // GUI app name
|
||||||
left += " ";
|
left += " ";
|
||||||
left += KTE_VERSION_STR;
|
left += KTE_VERSION_STR;
|
||||||
std::string fname = buf->Filename();
|
std::string fname;
|
||||||
if (!fname.empty()) {
|
try {
|
||||||
|
fname = ed.DisplayNameFor(*buf);
|
||||||
|
} catch (...) {
|
||||||
|
fname = buf->Filename();
|
||||||
try {
|
try {
|
||||||
fname = std::filesystem::path(fname).filename().string();
|
fname = std::filesystem::path(fname).filename().string();
|
||||||
} catch (...) {}
|
} catch (...) {}
|
||||||
} else {
|
|
||||||
fname = "[no name]";
|
|
||||||
}
|
}
|
||||||
left += " ";
|
left += " ";
|
||||||
|
// Insert buffer position prefix "[x/N] " before filename
|
||||||
|
{
|
||||||
|
std::size_t total = ed.BufferCount();
|
||||||
|
if (total > 0) {
|
||||||
|
std::size_t idx1 = ed.CurrentBufferIndex() + 1; // 1-based for display
|
||||||
|
left += "[";
|
||||||
|
left += std::to_string(static_cast<unsigned long long>(idx1));
|
||||||
|
left += "/";
|
||||||
|
left += std::to_string(static_cast<unsigned long long>(total));
|
||||||
|
left += "] ";
|
||||||
|
}
|
||||||
|
}
|
||||||
left += fname;
|
left += fname;
|
||||||
if (buf->Dirty())
|
if (buf->Dirty())
|
||||||
left += " *";
|
left += " *";
|
||||||
|
// Append total line count as "<n>L"
|
||||||
|
{
|
||||||
|
unsigned long lcount = static_cast<unsigned long>(buf->Rows().size());
|
||||||
|
left += " ";
|
||||||
|
left += std::to_string(lcount);
|
||||||
|
left += "L";
|
||||||
|
}
|
||||||
|
|
||||||
// Build right text (cursor/mark)
|
// Build right text (cursor/mark)
|
||||||
int row1 = static_cast<int>(buf->Cury()) + 1;
|
int row1 = static_cast<int>(buf->Cury()) + 1;
|
||||||
@@ -288,4 +406,153 @@ GUIRenderer::Draw(Editor &ed)
|
|||||||
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
ImGui::PopStyleVar(3);
|
ImGui::PopStyleVar(3);
|
||||||
|
|
||||||
|
// --- Visual File Picker overlay (GUI only) ---
|
||||||
|
if (ed.FilePickerVisible()) {
|
||||||
|
// Centered popup-style window that always fits within the current viewport
|
||||||
|
ImGuiViewport *vp2 = ImGui::GetMainViewport();
|
||||||
|
|
||||||
|
// Desired size, min size, and margins
|
||||||
|
const ImVec2 want(800.0f, 500.0f);
|
||||||
|
const ImVec2 min_sz(240.0f, 160.0f);
|
||||||
|
const float margin = 20.0f; // space from viewport edges
|
||||||
|
|
||||||
|
// Compute the maximum allowed size (viewport minus margins) and make sure it's not negative
|
||||||
|
ImVec2 max_sz(std::max(32.0f, vp2->Size.x - 2.0f * margin),
|
||||||
|
std::max(32.0f, vp2->Size.y - 2.0f * margin));
|
||||||
|
|
||||||
|
// Clamp desired size to [min_sz, max_sz]
|
||||||
|
ImVec2 size(std::min(want.x, max_sz.x), std::min(want.y, max_sz.y));
|
||||||
|
size.x = std::max(size.x, std::min(min_sz.x, max_sz.x));
|
||||||
|
size.y = std::max(size.y, std::min(min_sz.y, max_sz.y));
|
||||||
|
|
||||||
|
// Center within the viewport using the final size
|
||||||
|
ImVec2 pos(vp2->Pos.x + std::max(margin, (vp2->Size.x - size.x) * 0.5f),
|
||||||
|
vp2->Pos.y + std::max(margin, (vp2->Size.y - size.y) * 0.5f));
|
||||||
|
|
||||||
|
// On HiDPI displays (macOS Retina), ensure integer pixel alignment to avoid
|
||||||
|
// potential hit-test vs draw mismatches from sub-pixel positions.
|
||||||
|
pos.x = std::floor(pos.x + 0.5f);
|
||||||
|
pos.y = std::floor(pos.y + 0.5f);
|
||||||
|
size.x = std::floor(size.x + 0.5f);
|
||||||
|
size.y = std::floor(size.y + 0.5f);
|
||||||
|
|
||||||
|
ImGui::SetNextWindowPos(pos, ImGuiCond_Always);
|
||||||
|
ImGui::SetNextWindowSize(size, ImGuiCond_Always);
|
||||||
|
ImGuiWindowFlags wflags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
||||||
|
ImGuiWindowFlags_NoDocking;
|
||||||
|
bool open = true;
|
||||||
|
if (ImGui::Begin("File Picker", &open, wflags)) {
|
||||||
|
// Current directory
|
||||||
|
std::string curdir = ed.FilePickerDir();
|
||||||
|
if (curdir.empty()) {
|
||||||
|
try {
|
||||||
|
curdir = std::filesystem::current_path().string();
|
||||||
|
} catch (...) {
|
||||||
|
curdir = ".";
|
||||||
|
}
|
||||||
|
ed.SetFilePickerDir(curdir);
|
||||||
|
}
|
||||||
|
ImGui::TextUnformatted(curdir.c_str());
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Up")) {
|
||||||
|
try {
|
||||||
|
std::filesystem::path p(curdir);
|
||||||
|
if (p.has_parent_path()) {
|
||||||
|
ed.SetFilePickerDir(p.parent_path().string());
|
||||||
|
}
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Close")) {
|
||||||
|
ed.SetFilePickerVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
// Header
|
||||||
|
ImGui::TextUnformatted("Name");
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
// Scrollable list
|
||||||
|
ImGui::BeginChild("picker-list", ImVec2(0, 0), true);
|
||||||
|
|
||||||
|
// Build entries: directories first then files, alphabetical
|
||||||
|
struct Entry {
|
||||||
|
std::string name;
|
||||||
|
std::filesystem::path path;
|
||||||
|
bool is_dir;
|
||||||
|
};
|
||||||
|
std::vector<Entry> entries;
|
||||||
|
entries.reserve(256);
|
||||||
|
// Optional parent entry
|
||||||
|
try {
|
||||||
|
std::filesystem::path base(curdir);
|
||||||
|
std::error_code ec;
|
||||||
|
for (auto it = std::filesystem::directory_iterator(base, ec);
|
||||||
|
!ec && it != std::filesystem::directory_iterator(); it.increment(ec)) {
|
||||||
|
const auto &p = it->path();
|
||||||
|
std::string nm;
|
||||||
|
try {
|
||||||
|
nm = p.filename().string();
|
||||||
|
} catch (...) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (nm == "." || nm == "..")
|
||||||
|
continue;
|
||||||
|
bool is_dir = false;
|
||||||
|
std::error_code ec2;
|
||||||
|
is_dir = it->is_directory(ec2);
|
||||||
|
entries.push_back({nm, p, is_dir});
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
// ignore listing errors; show empty
|
||||||
|
}
|
||||||
|
std::sort(entries.begin(), entries.end(), [](const Entry &a, const Entry &b) {
|
||||||
|
if (a.is_dir != b.is_dir)
|
||||||
|
return a.is_dir && !b.is_dir;
|
||||||
|
return a.name < b.name;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Draw rows
|
||||||
|
int idx = 0;
|
||||||
|
for (const auto &e: entries) {
|
||||||
|
ImGui::PushID(idx++); // ensure unique/stable IDs even if names repeat
|
||||||
|
std::string label;
|
||||||
|
label.reserve(e.name.size() + 4);
|
||||||
|
if (e.is_dir)
|
||||||
|
label += "[";
|
||||||
|
label += e.name;
|
||||||
|
if (e.is_dir)
|
||||||
|
label += "]";
|
||||||
|
|
||||||
|
// Render selectable row
|
||||||
|
ImGui::Selectable(label.c_str(), false, ImGuiSelectableFlags_AllowDoubleClick);
|
||||||
|
|
||||||
|
// Activate based strictly on hover + mouse, to avoid any off-by-one due to click routing
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
if (e.is_dir && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
||||||
|
// Enter directory on double-click
|
||||||
|
ed.SetFilePickerDir(e.path.string());
|
||||||
|
} else if (!e.is_dir && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||||
|
// Open file on single click
|
||||||
|
std::string err;
|
||||||
|
if (!ed.OpenFile(e.path.string(), err)) {
|
||||||
|
ed.SetStatus(std::string("open: ") + err);
|
||||||
|
} else {
|
||||||
|
ed.SetStatus(std::string("Opened: ") + e.name);
|
||||||
|
}
|
||||||
|
ed.SetFilePickerVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
if (!open) {
|
||||||
|
ed.SetFilePickerVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
#include "Renderer.h"
|
#include "Renderer.h"
|
||||||
|
|
||||||
class GUIRenderer : public Renderer {
|
class GUIRenderer final : public Renderer {
|
||||||
public:
|
public:
|
||||||
GUIRenderer() = default;
|
GUIRenderer() = default;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
#include "GapBuffer.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "GapBuffer.h"
|
||||||
|
|
||||||
|
|
||||||
GapBuffer::GapBuffer() = default;
|
GapBuffer::GapBuffer() = default;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
||||||
|
|
||||||
class GapBuffer {
|
class GapBuffer {
|
||||||
public:
|
public:
|
||||||
GapBuffer();
|
GapBuffer();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include "Command.h"
|
#include "Command.h"
|
||||||
|
|
||||||
|
|
||||||
// Result of translating raw input into an editor command.
|
// Result of translating raw input into an editor command.
|
||||||
struct MappedInput {
|
struct MappedInput {
|
||||||
bool hasCommand = false;
|
bool hasCommand = false;
|
||||||
|
|||||||
137
KKeymap.cc
@@ -1,5 +1,8 @@
|
|||||||
#include "KKeymap.h"
|
#include <iostream>
|
||||||
#include <ncurses.h>
|
#include <ncurses.h>
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
|
#include "KKeymap.h"
|
||||||
|
|
||||||
|
|
||||||
auto
|
auto
|
||||||
@@ -13,17 +16,15 @@ KLookupKCommand(const int ascii_key, const bool ctrl, CommandId &out) -> bool
|
|||||||
switch (k_lower) {
|
switch (k_lower) {
|
||||||
case 'd':
|
case 'd':
|
||||||
out = CommandId::KillLine;
|
out = CommandId::KillLine;
|
||||||
return true; // C-k C-d
|
return true;
|
||||||
case 'x':
|
|
||||||
out = CommandId::SaveAndQuit;
|
|
||||||
return true; // C-k C-x
|
|
||||||
case 'q':
|
case 'q':
|
||||||
out = CommandId::QuitNow;
|
out = CommandId::QuitNow;
|
||||||
return true; // C-k C-q (quit immediately)
|
return true;
|
||||||
|
case 'x':
|
||||||
|
out = CommandId::SaveAndQuit;
|
||||||
|
return true;
|
||||||
default:
|
default:
|
||||||
// Important: do not return here — fall through to non-ctrl table
|
return false;
|
||||||
// so that C-k u/U still work even if Ctrl is (incorrectly) held
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,65 +34,78 @@ KLookupKCommand(const int ascii_key, const bool ctrl, CommandId &out) -> bool
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Non-control k-table (lowercased)
|
|
||||||
switch (k_lower) {
|
switch (k_lower) {
|
||||||
case 'j':
|
|
||||||
out = CommandId::JumpToMark;
|
|
||||||
return true; // C-k j
|
|
||||||
case 'f':
|
|
||||||
out = CommandId::FlushKillRing;
|
|
||||||
return true; // C-k f
|
|
||||||
case 'd':
|
|
||||||
out = CommandId::KillToEOL;
|
|
||||||
return true; // C-k d
|
|
||||||
case 'y':
|
|
||||||
out = CommandId::Yank;
|
|
||||||
return true; // C-k y
|
|
||||||
case 's':
|
|
||||||
out = CommandId::Save;
|
|
||||||
return true; // C-k s
|
|
||||||
case 'e':
|
|
||||||
out = CommandId::OpenFileStart;
|
|
||||||
return true; // C-k e (open file)
|
|
||||||
case 'b':
|
|
||||||
out = CommandId::BufferSwitchStart;
|
|
||||||
return true; // C-k b (switch buffer by name)
|
|
||||||
case 'c':
|
|
||||||
out = CommandId::BufferClose;
|
|
||||||
return true; // C-k c (close current buffer)
|
|
||||||
case 'n':
|
|
||||||
out = CommandId::BufferPrev;
|
|
||||||
return true; // C-k n (switch to previous buffer)
|
|
||||||
case 'x':
|
|
||||||
out = CommandId::SaveAndQuit;
|
|
||||||
return true; // C-k x
|
|
||||||
case 'q':
|
|
||||||
out = CommandId::Quit;
|
|
||||||
return true; // C-k q
|
|
||||||
case 'p':
|
|
||||||
out = CommandId::BufferNext;
|
|
||||||
return true; // C-k p (switch to next buffer)
|
|
||||||
case 'u':
|
|
||||||
out = CommandId::Undo;
|
|
||||||
return true; // C-k u (undo)
|
|
||||||
case '-':
|
|
||||||
out = CommandId::UnindentRegion;
|
|
||||||
return true; // C-k - (unindent region)
|
|
||||||
case '=':
|
|
||||||
out = CommandId::IndentRegion;
|
|
||||||
return true; // C-k = (indent region)
|
|
||||||
case 'l':
|
|
||||||
out = CommandId::ReloadBuffer;
|
|
||||||
return true; // C-k l (reload buffer)
|
|
||||||
case 'a':
|
case 'a':
|
||||||
out = CommandId::MarkAllAndJumpEnd;
|
out = CommandId::MarkAllAndJumpEnd;
|
||||||
return true; // C-k a (mark all and jump to end)
|
return true;
|
||||||
|
case 'b':
|
||||||
|
out = CommandId::BufferSwitchStart;
|
||||||
|
return true;
|
||||||
|
case 'c':
|
||||||
|
out = CommandId::BufferClose;
|
||||||
|
return true;
|
||||||
|
case 'd':
|
||||||
|
out = CommandId::KillToEOL;
|
||||||
|
return true;
|
||||||
|
case 'e':
|
||||||
|
out = CommandId::OpenFileStart;
|
||||||
|
return true;
|
||||||
|
case 'E':
|
||||||
|
std::cerr << "E is not a valid command" << std::endl;
|
||||||
|
return false;
|
||||||
|
case 'f':
|
||||||
|
out = CommandId::FlushKillRing;
|
||||||
|
return true;
|
||||||
case 'g':
|
case 'g':
|
||||||
out = CommandId::JumpToLine;
|
out = CommandId::JumpToLine;
|
||||||
return true; // C-k g (goto line)
|
return true;
|
||||||
|
case 'j':
|
||||||
|
out = CommandId::JumpToMark;
|
||||||
|
return true;
|
||||||
|
case 'l':
|
||||||
|
out = CommandId::ReloadBuffer;
|
||||||
|
return true;
|
||||||
|
case 'n':
|
||||||
|
out = CommandId::BufferPrev;
|
||||||
|
return true;
|
||||||
|
case 'o':
|
||||||
|
out = CommandId::ChangeWorkingDirectory;
|
||||||
|
return true;
|
||||||
|
case 'p':
|
||||||
|
out = CommandId::BufferNext;
|
||||||
|
return true;
|
||||||
|
case 'q':
|
||||||
|
out = CommandId::Quit;
|
||||||
|
return true;
|
||||||
|
case 's':
|
||||||
|
out = CommandId::Save;
|
||||||
|
return true;
|
||||||
|
case 'u':
|
||||||
|
out = CommandId::Undo;
|
||||||
|
return true;
|
||||||
|
case 'v':
|
||||||
|
out = CommandId::VisualFilePickerToggle;
|
||||||
|
return true;
|
||||||
|
case 'w':
|
||||||
|
out = CommandId::ShowWorkingDirectory;
|
||||||
|
return true;
|
||||||
|
case 'x':
|
||||||
|
out = CommandId::SaveAndQuit;
|
||||||
|
return true;
|
||||||
|
case 'y':
|
||||||
|
out = CommandId::Yank;
|
||||||
|
return true;
|
||||||
|
case '-':
|
||||||
|
out = CommandId::UnindentRegion;
|
||||||
|
return true;
|
||||||
|
case '=':
|
||||||
|
out = CommandId::IndentRegion;
|
||||||
|
return true;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3) Non-control k-table (lowercased)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,9 +151,6 @@ KLookupCtrlCommand(const int ascii_key, CommandId &out) -> bool
|
|||||||
case 'g':
|
case 'g':
|
||||||
out = CommandId::Refresh;
|
out = CommandId::Refresh;
|
||||||
return true;
|
return true;
|
||||||
case 'x':
|
|
||||||
out = CommandId::SaveAndQuit; // direct C-x mapping (GUI had this)
|
|
||||||
return true;
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "Command.h"
|
#include "Command.h"
|
||||||
|
|
||||||
|
|
||||||
// Lookup the command to execute after a C-k prefix.
|
// Lookup the command to execute after a C-k prefix.
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// - ascii_key: ASCII code of the key, preferably lowercased if it's a letter.
|
// - ascii_key: ASCII code of the key, preferably lowercased if it's a letter.
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
class PieceTable {
|
class PieceTable {
|
||||||
public:
|
public:
|
||||||
PieceTable();
|
PieceTable();
|
||||||
|
|||||||
22
README.md
@@ -2,8 +2,8 @@ kte — Kyle's Text Editor
|
|||||||
|
|
||||||
Vision
|
Vision
|
||||||
-------
|
-------
|
||||||
kte will be a small, fast, and understandable text editor with a
|
kte is a small, fast, and understandable text editor with a
|
||||||
terminal<EFBFBD>first UX and an optional ImGui GUI. It modernizes the
|
terminal-first UX and an optional ImGui GUI. It modernizes the
|
||||||
original ke editor while preserving its familiar WordStar/VDE‑style
|
original ke editor while preserving its familiar WordStar/VDE‑style
|
||||||
command model and Emacs‑influenced ergonomics. The focus is on
|
command model and Emacs‑influenced ergonomics. The focus is on
|
||||||
simplicity of design, excellent latency, and pragmatic features you
|
simplicity of design, excellent latency, and pragmatic features you
|
||||||
@@ -80,6 +80,15 @@ Interfaces
|
|||||||
- GUI: an optional ImGui‑based frontend that embeds the same editor
|
- GUI: an optional ImGui‑based frontend that embeds the same editor
|
||||||
core.
|
core.
|
||||||
|
|
||||||
|
Man pages
|
||||||
|
---------
|
||||||
|
|
||||||
|
- Terminal editor: `docs/kte.1` (view locally with `man -l docs/kte.1`)
|
||||||
|
- GUI frontend: `docs/kge.1` (view locally with `man -l docs/kge.1`)
|
||||||
|
|
||||||
|
The `ke` keybinding reference remains the canonical source for
|
||||||
|
commands while kte evolves: see `docs/ke.md`.
|
||||||
|
|
||||||
Architecture (intended)
|
Architecture (intended)
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
@@ -180,6 +189,15 @@ Run:
|
|||||||
./cmake-build-debug/kte [files]
|
./cmake-build-debug/kte [files]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you configured the GUI, you can also run the GUI-first target (when
|
||||||
|
built as `kge`) or request the GUI from `kte`:
|
||||||
|
|
||||||
|
```
|
||||||
|
./cmake-build-debug/kte --gui [files]
|
||||||
|
# or if built/installed as a separate GUI target
|
||||||
|
./cmake-build-debug/kge [files]
|
||||||
|
```
|
||||||
|
|
||||||
GUI build example
|
GUI build example
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#include <unistd.h>
|
|
||||||
#include <termios.h>
|
|
||||||
#include <ncurses.h>
|
#include <ncurses.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "Editor.h"
|
|
||||||
#include "Command.h"
|
|
||||||
#include "TerminalFrontend.h"
|
#include "TerminalFrontend.h"
|
||||||
|
#include "Command.h"
|
||||||
|
#include "Editor.h"
|
||||||
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|||||||
@@ -4,10 +4,11 @@
|
|||||||
#ifndef KTE_TERMINAL_FRONTEND_H
|
#ifndef KTE_TERMINAL_FRONTEND_H
|
||||||
#define KTE_TERMINAL_FRONTEND_H
|
#define KTE_TERMINAL_FRONTEND_H
|
||||||
|
|
||||||
|
#include <termios.h>
|
||||||
|
|
||||||
#include "Frontend.h"
|
#include "Frontend.h"
|
||||||
#include "TerminalInputHandler.h"
|
#include "TerminalInputHandler.h"
|
||||||
#include "TerminalRenderer.h"
|
#include "TerminalRenderer.h"
|
||||||
#include <termios.h>
|
|
||||||
|
|
||||||
|
|
||||||
class TerminalFrontend final : public Frontend {
|
class TerminalFrontend final : public Frontend {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#include <ncurses.h>
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <ncurses.h>
|
||||||
|
|
||||||
#include "KKeymap.h"
|
|
||||||
#include "TerminalInputHandler.h"
|
#include "TerminalInputHandler.h"
|
||||||
|
#include "KKeymap.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr int
|
constexpr int
|
||||||
|
|||||||
@@ -4,8 +4,6 @@
|
|||||||
#ifndef KTE_TERMINAL_INPUT_HANDLER_H
|
#ifndef KTE_TERMINAL_INPUT_HANDLER_H
|
||||||
#define KTE_TERMINAL_INPUT_HANDLER_H
|
#define KTE_TERMINAL_INPUT_HANDLER_H
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
#include "InputHandler.h"
|
#include "InputHandler.h"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
#include "TerminalRenderer.h"
|
|
||||||
|
|
||||||
#include <ncurses.h>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <string>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cstdio>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <ncurses.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "Editor.h"
|
#include "TerminalRenderer.h"
|
||||||
#include "Buffer.h"
|
#include "Buffer.h"
|
||||||
|
#include "Editor.h"
|
||||||
|
|
||||||
// Version string expected to be provided by build system as KTE_VERSION_STR
|
// Version string expected to be provided by build system as KTE_VERSION_STR
|
||||||
#ifndef KTE_VERSION_STR
|
#ifndef KTE_VERSION_STR
|
||||||
# define KTE_VERSION_STR "dev"
|
# define KTE_VERSION_STR "dev"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
TerminalRenderer::TerminalRenderer() = default;
|
TerminalRenderer::TerminalRenderer() = default;
|
||||||
|
|
||||||
TerminalRenderer::~TerminalRenderer() = default;
|
TerminalRenderer::~TerminalRenderer() = default;
|
||||||
@@ -168,21 +168,40 @@ TerminalRenderer::Draw(Editor &ed)
|
|||||||
const Buffer *b = buf;
|
const Buffer *b = buf;
|
||||||
std::string fname;
|
std::string fname;
|
||||||
if (b) {
|
if (b) {
|
||||||
fname = b->Filename();
|
|
||||||
}
|
|
||||||
if (!fname.empty()) {
|
|
||||||
try {
|
try {
|
||||||
fname = std::filesystem::path(fname).filename().string();
|
fname = ed.DisplayNameFor(*b);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
// keep original on any error
|
fname = b->Filename();
|
||||||
|
try {
|
||||||
|
fname = std::filesystem::path(fname).filename().string();
|
||||||
|
} catch (...) {}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fname = "[no name]";
|
fname = "[no name]";
|
||||||
}
|
}
|
||||||
left += " ";
|
left += " ";
|
||||||
|
// Insert buffer position prefix "[x/N] " before filename
|
||||||
|
{
|
||||||
|
std::size_t total = ed.BufferCount();
|
||||||
|
if (total > 0) {
|
||||||
|
std::size_t idx1 = ed.CurrentBufferIndex() + 1; // human-friendly 1-based
|
||||||
|
left += "[";
|
||||||
|
left += std::to_string(static_cast<unsigned long long>(idx1));
|
||||||
|
left += "/";
|
||||||
|
left += std::to_string(static_cast<unsigned long long>(total));
|
||||||
|
left += "] ";
|
||||||
|
}
|
||||||
|
}
|
||||||
left += fname;
|
left += fname;
|
||||||
if (b && b->Dirty())
|
if (b && b->Dirty())
|
||||||
left += " *";
|
left += " *";
|
||||||
|
// Append total line count as "<n>L"
|
||||||
|
if (b) {
|
||||||
|
unsigned long lcount = static_cast<unsigned long>(b->Rows().size());
|
||||||
|
left += " ";
|
||||||
|
left += std::to_string(lcount);
|
||||||
|
left += "L";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build right segment (cursor and mark)
|
// Build right segment (cursor and mark)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#include "TestFrontend.h"
|
#include "TestFrontend.h"
|
||||||
#include "Editor.h"
|
|
||||||
#include "Command.h"
|
#include "Command.h"
|
||||||
#include <iostream>
|
#include "Editor.h"
|
||||||
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|||||||
@@ -4,11 +4,12 @@
|
|||||||
#ifndef KTE_TEST_INPUT_HANDLER_H
|
#ifndef KTE_TEST_INPUT_HANDLER_H
|
||||||
#define KTE_TEST_INPUT_HANDLER_H
|
#define KTE_TEST_INPUT_HANDLER_H
|
||||||
|
|
||||||
#include "InputHandler.h"
|
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
|
||||||
|
#include "InputHandler.h"
|
||||||
|
|
||||||
class TestInputHandler : public InputHandler {
|
|
||||||
|
class TestInputHandler final : public InputHandler {
|
||||||
public:
|
public:
|
||||||
TestInputHandler() = default;
|
TestInputHandler() = default;
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ public:
|
|||||||
void QueueText(const std::string &text);
|
void QueueText(const std::string &text);
|
||||||
|
|
||||||
|
|
||||||
bool IsEmpty() const
|
[[nodiscard]] bool IsEmpty() const
|
||||||
{
|
{
|
||||||
return queue_.empty();
|
return queue_.empty();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#include "TestRenderer.h"
|
#include "TestRenderer.h"
|
||||||
#include "Editor.h"
|
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -4,11 +4,12 @@
|
|||||||
#ifndef KTE_TEST_RENDERER_H
|
#ifndef KTE_TEST_RENDERER_H
|
||||||
#define KTE_TEST_RENDERER_H
|
#define KTE_TEST_RENDERER_H
|
||||||
|
|
||||||
#include "Renderer.h"
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include "Renderer.h"
|
||||||
|
|
||||||
class TestRenderer : public Renderer {
|
|
||||||
|
class TestRenderer final : public Renderer {
|
||||||
public:
|
public:
|
||||||
TestRenderer() = default;
|
TestRenderer() = default;
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ public:
|
|||||||
void Draw(Editor &ed) override;
|
void Draw(Editor &ed) override;
|
||||||
|
|
||||||
|
|
||||||
std::size_t GetDrawCount() const
|
[[nodiscard]] std::size_t GetDrawCount() const
|
||||||
{
|
{
|
||||||
return draw_count_;
|
return draw_count_;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
#ifndef KTE_UNDONODE_H
|
#ifndef KTE_UNDONODE_H
|
||||||
#define KTE_UNDONODE_H
|
#define KTE_UNDONODE_H
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ UndoSystem::Begin(UndoType type)
|
|||||||
if (type == UndoType::Delete) {
|
if (type == UndoType::Delete) {
|
||||||
// Support batching both forward deletes (DeleteChar) and backspace (prepend case)
|
// Support batching both forward deletes (DeleteChar) and backspace (prepend case)
|
||||||
// Forward delete: cursor stays at anchor col; expected == col
|
// Forward delete: cursor stays at anchor col; expected == col
|
||||||
std::size_t anchor = static_cast<std::size_t>(tree_.pending->col);
|
const auto anchor = static_cast<std::size_t>(tree_.pending->col);
|
||||||
if (anchor + tree_.pending->text.size() == static_cast<std::size_t>(col)) {
|
if (anchor + tree_.pending->text.size() == static_cast<std::size_t>(col)) {
|
||||||
pending_prepend_ = false;
|
pending_prepend_ = false;
|
||||||
return; // keep batching forward delete
|
return; // keep batching forward delete
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
#define KTE_UNDOSYSTEM_H
|
#define KTE_UNDOSYSTEM_H
|
||||||
|
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
#include "UndoTree.h"
|
#include "UndoTree.h"
|
||||||
|
|
||||||
|
|
||||||
class Buffer;
|
class Buffer;
|
||||||
|
|
||||||
class UndoSystem {
|
class UndoSystem {
|
||||||
@@ -39,7 +41,6 @@ private:
|
|||||||
|
|
||||||
void update_dirty_flag();
|
void update_dirty_flag();
|
||||||
|
|
||||||
private:
|
|
||||||
Buffer *buf_;
|
Buffer *buf_;
|
||||||
UndoTree &tree_;
|
UndoTree &tree_;
|
||||||
// Internal hint for Delete batching: whether next Append() should prepend
|
// Internal hint for Delete batching: whether next Append() should prepend
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#define KTE_UNDOTREE_H
|
#define KTE_UNDOTREE_H
|
||||||
|
|
||||||
#include "UndoNode.h"
|
#include "UndoNode.h"
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
struct UndoTree {
|
struct UndoTree {
|
||||||
UndoNode *root = nullptr; // first edit ever
|
UndoNode *root = nullptr; // first edit ever
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
<string>en</string>
|
<string>en</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>kge</string>
|
<string>kge</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>@MACOSX_BUNDLE_ICON_FILE@</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>@KGE_BUNDLE_ID@</string>
|
<string>@KGE_BUNDLE_ID@</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
@@ -23,4 +25,4 @@
|
|||||||
<key>NSHighResolutionCapable</key>
|
<key>NSHighResolutionCapable</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
51
default-gui.nix
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
stdenv,
|
||||||
|
cmake,
|
||||||
|
ncurses,
|
||||||
|
SDL2,
|
||||||
|
libGL,
|
||||||
|
xorg,
|
||||||
|
installShellFiles,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
cmakeContent = builtins.readFile ./CMakeLists.txt;
|
||||||
|
cmakeLines = lib.splitString "\n" cmakeContent;
|
||||||
|
versionLine = lib.findFirst (l: builtins.match ".*set\\(KTE_VERSION \".+\"\\).*" l != null) (throw "KTE_VERSION not found in CMakeLists.txt") cmakeLines;
|
||||||
|
version = builtins.head (builtins.match ".*set\\(KTE_VERSION \"(.+)\"\\).*" versionLine);
|
||||||
|
in
|
||||||
|
stdenv.mkDerivation {
|
||||||
|
pname = "kte";
|
||||||
|
inherit version;
|
||||||
|
|
||||||
|
src = lib.cleanSource ./.;
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
cmake
|
||||||
|
ncurses
|
||||||
|
SDL2
|
||||||
|
libGL
|
||||||
|
xorg.libX11
|
||||||
|
installShellFiles
|
||||||
|
];
|
||||||
|
|
||||||
|
cmakeFlags = [
|
||||||
|
"-DBUILD_GUI=ON"
|
||||||
|
"-DCMAKE_BUILD_TYPE=Debug"
|
||||||
|
];
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
|
||||||
|
mkdir -p $out/bin
|
||||||
|
cp kte $out/bin/
|
||||||
|
cp kge $out/bin/
|
||||||
|
|
||||||
|
installManPage ../docs/kte.1
|
||||||
|
installManPage ../docs/kge.1
|
||||||
|
|
||||||
|
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
}
|
||||||
42
default-nogui.nix
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
stdenv,
|
||||||
|
cmake,
|
||||||
|
ncurses,
|
||||||
|
installShellFiles,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
cmakeContent = builtins.readFile ./CMakeLists.txt;
|
||||||
|
cmakeLines = lib.splitString "\n" cmakeContent;
|
||||||
|
versionLine = lib.findFirst (l: builtins.match ".*set\\(KTE_VERSION \".+\"\\).*" l != null) (throw "KTE_VERSION not found in CMakeLists.txt") cmakeLines;
|
||||||
|
version = builtins.head (builtins.match ".*set\\(KTE_VERSION \"(.+)\"\\).*" versionLine);
|
||||||
|
in
|
||||||
|
stdenv.mkDerivation {
|
||||||
|
pname = "kte";
|
||||||
|
inherit version;
|
||||||
|
|
||||||
|
src = lib.cleanSource ./.;
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
cmake
|
||||||
|
ncurses
|
||||||
|
installShellFiles
|
||||||
|
];
|
||||||
|
|
||||||
|
cmakeFlags = [
|
||||||
|
"-DBUILD_GUI=OFF"
|
||||||
|
"-DCMAKE_BUILD_TYPE=Debug"
|
||||||
|
];
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
|
||||||
|
mkdir -p $out/bin
|
||||||
|
cp kte $out/bin/
|
||||||
|
|
||||||
|
installManPage ../docs/kte.1
|
||||||
|
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
}
|
||||||
44
default.nix
@@ -1,24 +1,50 @@
|
|||||||
# default.nix
|
{
|
||||||
|
lib,
|
||||||
|
stdenv,
|
||||||
|
cmake,
|
||||||
|
ncurses,
|
||||||
|
SDL2,
|
||||||
|
libGL,
|
||||||
|
xorg,
|
||||||
|
installShellFiles,
|
||||||
|
...
|
||||||
|
}:
|
||||||
let
|
let
|
||||||
pkgs = import <nixpkgs> {};
|
cmakeContent = builtins.readFile ./CMakeLists.txt;
|
||||||
|
cmakeLines = lib.splitString "\n" cmakeContent;
|
||||||
|
versionLine = lib.findFirst (l: builtins.match ".*set\\(KTE_VERSION \".+\"\\).*" l != null) (throw "KTE_VERSION not found in CMakeLists.txt") cmakeLines;
|
||||||
|
version = builtins.head (builtins.match ".*set\\(KTE_VERSION \"(.+)\"\\).*" versionLine);
|
||||||
in
|
in
|
||||||
pkgs.stdenv.mkDerivation {
|
stdenv.mkDerivation {
|
||||||
pname = "kte";
|
pname = "kte";
|
||||||
version = "0.1.0";
|
inherit version;
|
||||||
|
|
||||||
src = ./.;
|
src = lib.cleanSource ./.;
|
||||||
|
|
||||||
nativeBuildInputs = [ pkgs.cmake pkgs.pkg-config ];
|
nativeBuildInputs = [
|
||||||
buildInputs = with pkgs; [
|
cmake
|
||||||
ncurses
|
ncurses
|
||||||
SDL2
|
SDL2
|
||||||
libGL
|
libGL
|
||||||
xorg.libX11
|
xorg.libX11
|
||||||
|
installShellFiles
|
||||||
];
|
];
|
||||||
|
|
||||||
cmakeFlags = [
|
cmakeFlags = [
|
||||||
"-DBUILD_GUI=ON"
|
"-DBUILD_GUI=ON"
|
||||||
"-DCURSES_NEED_NCURSES=TRUE"
|
"-DCMAKE_BUILD_TYPE=Debug"
|
||||||
"-DCURSES_NEED_WIDE=TRUE"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
|
||||||
|
mkdir -p $out/bin
|
||||||
|
cp kte $out/bin/
|
||||||
|
cp kge $out/bin/
|
||||||
|
|
||||||
|
installManPage ../docs/kte.1
|
||||||
|
installManPage ../docs/kge.1
|
||||||
|
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,11 @@ kge \- Kyle's Graphical Editor (GUI-first)
|
|||||||
is the GUI-first build target of Kyle's Text Editor. It shares the same
|
is the GUI-first build target of Kyle's Text Editor. It shares the same
|
||||||
editor core and command model as
|
editor core and command model as
|
||||||
.BR kte (1),
|
.BR kte (1),
|
||||||
but defaults to the graphical ImGui frontend when available. A terminal
|
and defaults to the graphical ImGui frontend when available. A terminal
|
||||||
(ncurses) frontend is also available and can be requested explicitly.
|
(ncurses) frontend is also available and can be requested explicitly with
|
||||||
|
.B --term
|
||||||
|
or by invoking
|
||||||
|
.BR kte (1).
|
||||||
|
|
||||||
If one or more
|
If one or more
|
||||||
.I files
|
.I files
|
||||||
@@ -199,6 +202,8 @@ Open using the terminal frontend from kge:
|
|||||||
.BR kte (1),
|
.BR kte (1),
|
||||||
.I docs/ke.md
|
.I docs/ke.md
|
||||||
(project keybinding manual)
|
(project keybinding manual)
|
||||||
|
.br
|
||||||
|
Project homepage: https://github.com/wntrmute/kte
|
||||||
.SH BUGS
|
.SH BUGS
|
||||||
Report issues on the project tracker. Some behaviors are inherited from
|
Report issues on the project tracker. Some behaviors are inherited from
|
||||||
ke and may evolve over time; see the manual for notes.
|
ke and may evolve over time; see the manual for notes.
|
||||||
|
|||||||
13
docs/kte.1
@@ -16,8 +16,15 @@ kte \- Kyle's Text Editor (terminal-first)
|
|||||||
.B kte
|
.B kte
|
||||||
is a small, fast, and understandable text editor with a terminal-first
|
is a small, fast, and understandable text editor with a terminal-first
|
||||||
experience. It preserves ke's WordStar/VDE-style command model with
|
experience. It preserves ke's WordStar/VDE-style command model with
|
||||||
Emacs-influenced ergonomics. The core uses ncurses in the terminal and can
|
Emacs-influenced ergonomics. The core uses ncurses in the terminal.
|
||||||
optionally run with a GUI frontend if built.
|
|
||||||
|
By default, .B kte
|
||||||
|
runs in the terminal (ncurses) frontend. If the binary was built with GUI
|
||||||
|
support, the same editor core can be shown with an ImGui-based GUI by passing
|
||||||
|
.B --gui
|
||||||
|
or by invoking the GUI-first target
|
||||||
|
.BR kge (1)
|
||||||
|
when available.
|
||||||
|
|
||||||
If one or more
|
If one or more
|
||||||
.I files
|
.I files
|
||||||
@@ -194,6 +201,8 @@ Force GUI frontend (if available):
|
|||||||
.BR kge (1),
|
.BR kge (1),
|
||||||
.I docs/ke.md
|
.I docs/ke.md
|
||||||
(project keybinding manual)
|
(project keybinding manual)
|
||||||
|
.br
|
||||||
|
Project homepage: https://github.com/wntrmute/kte
|
||||||
.SH BUGS
|
.SH BUGS
|
||||||
Incremental search currently restarts from the top on each invocation; see
|
Incremental search currently restarts from the top on each invocation; see
|
||||||
\(lqKnown behavior\(rq in the ke manual. Report issues on the project tracker.
|
\(lqKnown behavior\(rq in the ke manual. Report issues on the project tracker.
|
||||||
|
|||||||
55
flake-gui.nix
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# flake.nix
|
||||||
|
{
|
||||||
|
description = "kte ImGui/SDL2 text editor";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, flake-utils }:
|
||||||
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
in {
|
||||||
|
packages.default = pkgs.stdenv.mkDerivation {
|
||||||
|
pname = "kte";
|
||||||
|
version = "0.1.0";
|
||||||
|
src = ./.;
|
||||||
|
|
||||||
|
nativeBuildInputs = [ pkgs.cmake pkgs.pkg-config ];
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
ncurses
|
||||||
|
SDL2
|
||||||
|
libGL
|
||||||
|
xorg.libX11
|
||||||
|
];
|
||||||
|
|
||||||
|
cmakeFlags = [
|
||||||
|
"-DBUILD_GUI=ON"
|
||||||
|
"-DCURSES_NEED_NCURSES=TRUE"
|
||||||
|
"-DCURSES_NEED_WIDE=TRUE"
|
||||||
|
];
|
||||||
|
|
||||||
|
# Alternative (even stronger): completely hide the broken module
|
||||||
|
preConfigure = ''
|
||||||
|
# If the project ships its own FindSDL2.cmake in cmake/, hide it
|
||||||
|
if [ -f cmake/FindSDL2.cmake ]; then
|
||||||
|
mv cmake/FindSDL2.cmake cmake/FindSDL2.cmake.disabled
|
||||||
|
echo "Disabled bundled FindSDL2.cmake"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = with pkgs.lib; {
|
||||||
|
description = "kte ImGui/SDL2 GUI editor";
|
||||||
|
mainProgram = "kte";
|
||||||
|
platforms = platforms.linux;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
inputsFrom = [ self.packages.${system}.default ];
|
||||||
|
packages = with pkgs; [ gdb clang-tools ];
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
38
flake.lock
generated
@@ -1,34 +1,16 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"flake-utils": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1731533236,
|
|
||||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1764242076,
|
"lastModified": 1764242076,
|
||||||
"narHash": "sha256-sKoIWfnijJ0+9e4wRvIgm/HgE27bzwQxcEmo2J/gNpI=",
|
"narHash": "sha256-sKoIWfnijJ0+9e4wRvIgm/HgE27bzwQxcEmo2J/gNpI=",
|
||||||
"owner": "NixOS",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "2fad6eac6077f03fe109c4d4eb171cf96791faa4",
|
"rev": "2fad6eac6077f03fe109c4d4eb171cf96791faa4",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "nixos",
|
||||||
"ref": "nixos-unstable",
|
"ref": "nixos-unstable",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
@@ -36,24 +18,8 @@
|
|||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-utils": "flake-utils",
|
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
|||||||
66
flake.nix
@@ -1,55 +1,21 @@
|
|||||||
# flake.nix
|
|
||||||
{
|
{
|
||||||
description = "kte ImGui/SDL2 text editor";
|
description = "Kyle's Text Editor";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, flake-utils }:
|
outputs =
|
||||||
flake-utils.lib.eachDefaultSystem (system:
|
{ self, nixpkgs }:
|
||||||
let
|
let
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
pkgs = import nixpkgs { system = "x86_64-linux"; };
|
||||||
in {
|
in
|
||||||
packages.default = pkgs.stdenv.mkDerivation {
|
{
|
||||||
pname = "kte";
|
packages.x86_64-linux = {
|
||||||
version = "0.1.0";
|
default = pkgs.callPackage ./default-nogui.nix { };
|
||||||
src = ./.;
|
kge = pkgs.callPackage ./default-gui.nix { };
|
||||||
|
kte = pkgs.callPackage ./default-nogui.nix { };
|
||||||
nativeBuildInputs = [ pkgs.cmake pkgs.pkg-config ];
|
full = pkgs.callPackage ./default.nix { };
|
||||||
buildInputs = with pkgs; [
|
};
|
||||||
ncurses
|
};
|
||||||
SDL2
|
}
|
||||||
libGL
|
|
||||||
xorg.libX11
|
|
||||||
];
|
|
||||||
|
|
||||||
cmakeFlags = [
|
|
||||||
"-DBUILD_GUI=ON"
|
|
||||||
"-DCURSES_NEED_NCURSES=TRUE"
|
|
||||||
"-DCURSES_NEED_WIDE=TRUE"
|
|
||||||
];
|
|
||||||
|
|
||||||
# Alternative (even stronger): completely hide the broken module
|
|
||||||
preConfigure = ''
|
|
||||||
# If the project ships its own FindSDL2.cmake in cmake/, hide it
|
|
||||||
if [ -f cmake/FindSDL2.cmake ]; then
|
|
||||||
mv cmake/FindSDL2.cmake cmake/FindSDL2.cmake.disabled
|
|
||||||
echo "Disabled bundled FindSDL2.cmake"
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
|
|
||||||
meta = with pkgs.lib; {
|
|
||||||
description = "kte ImGui/SDL2 GUI editor";
|
|
||||||
mainProgram = "kte";
|
|
||||||
platforms = platforms.linux;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
devShells.default = pkgs.mkShell {
|
|
||||||
inputsFrom = [ self.packages.${system}.default ];
|
|
||||||
packages = with pkgs; [ gdb clang-tools ];
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
BIN
kge.iconset/icon_128x128.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
kge.iconset/icon_128x128@2x.png
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
kge.iconset/icon_16x16.png
Normal file
|
After Width: | Height: | Size: 911 B |
BIN
kge.iconset/icon_16x16@2x.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
kge.iconset/icon_256x256.png
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
kge.iconset/icon_256x256@2x.png
Normal file
|
After Width: | Height: | Size: 483 KiB |
BIN
kge.iconset/icon_32x32.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
kge.iconset/icon_32x32@2x.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
kge.iconset/icon_512x512.png
Normal file
|
After Width: | Height: | Size: 483 KiB |
BIN
kge.iconset/icon_512x512@2x.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
130
main.cc
@@ -1,17 +1,18 @@
|
|||||||
|
#include <cctype>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <getopt.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
|
||||||
#include <cctype>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <getopt.h>
|
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <cstdio>
|
#include <string>
|
||||||
|
#include <unistd.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
#include "Editor.h"
|
|
||||||
#include "Command.h"
|
#include "Command.h"
|
||||||
|
#include "Editor.h"
|
||||||
#include "Frontend.h"
|
#include "Frontend.h"
|
||||||
#include "TerminalFrontend.h"
|
#include "TerminalFrontend.h"
|
||||||
|
|
||||||
#if defined(KTE_BUILD_GUI)
|
#if defined(KTE_BUILD_GUI)
|
||||||
#include "GUIFrontend.h"
|
#include "GUIFrontend.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -21,6 +22,7 @@
|
|||||||
# define KTE_VERSION_STR "devel"
|
# define KTE_VERSION_STR "devel"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
PrintUsage(const char *prog)
|
PrintUsage(const char *prog)
|
||||||
{
|
{
|
||||||
@@ -33,109 +35,6 @@ PrintUsage(const char *prog)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#if defined(KTE_BUILD_GUI)
|
|
||||||
// Detach the process from the controlling terminal when running the GUI so the
|
|
||||||
// launching terminal can be closed. This mirrors typical GUI app behavior on
|
|
||||||
// POSIX systems.
|
|
||||||
static void
|
|
||||||
DetachFromTerminalIfGUI(bool use_gui)
|
|
||||||
{
|
|
||||||
#if defined(__APPLE__) || defined(__linux__) || defined(__unix__)
|
|
||||||
if (!use_gui)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Ignore SIGHUP so closing the terminal won't terminate us.
|
|
||||||
signal(SIGHUP, SIG_IGN);
|
|
||||||
|
|
||||||
// Helper: redirect stdio to /dev/null and optionally close extra FDs.
|
|
||||||
auto redirect_stdio_and_close = []() {
|
|
||||||
// Reset file mode creation mask and working directory to a safe default
|
|
||||||
umask(0);
|
|
||||||
chdir("/");
|
|
||||||
|
|
||||||
FILE *fnull_r = fopen("/dev/null", "r");
|
|
||||||
if (fnull_r) {
|
|
||||||
dup2(fileno(fnull_r), STDIN_FILENO);
|
|
||||||
}
|
|
||||||
FILE *fnull_w = fopen("/dev/null", "w");
|
|
||||||
if (fnull_w) {
|
|
||||||
dup2(fileno(fnull_w), STDOUT_FILENO);
|
|
||||||
dup2(fileno(fnull_w), STDERR_FILENO);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close any other inherited FDs to avoid keeping terminal/pty or pipes open
|
|
||||||
long max_fd = sysconf(_SC_OPEN_MAX);
|
|
||||||
if (max_fd < 0)
|
|
||||||
max_fd = 256; // conservative fallback
|
|
||||||
for (long fd = 3; fd < max_fd; ++fd) {
|
|
||||||
close(static_cast<int>(fd));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#if defined(__APPLE__)
|
|
||||||
// macOS: daemon(3) is deprecated and treated as an error with -Werror.
|
|
||||||
// Use double-fork + setsid and redirect stdio to /dev/null.
|
|
||||||
pid_t pid = fork();
|
|
||||||
if (pid < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pid > 0) {
|
|
||||||
_exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setsid() < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pid_t pid2 = fork();
|
|
||||||
if (pid2 < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pid2 > 0) {
|
|
||||||
_exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
redirect_stdio_and_close();
|
|
||||||
#else
|
|
||||||
// Prefer daemon(3) on non-Apple POSIX; fall back to manual detach if it fails.
|
|
||||||
if (daemon(0, 0) == 0) {
|
|
||||||
redirect_stdio_and_close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pid_t pid = fork();
|
|
||||||
if (pid < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pid > 0) {
|
|
||||||
_exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setsid() < 0) {
|
|
||||||
// bogus check
|
|
||||||
}
|
|
||||||
|
|
||||||
pid_t pid2 = fork();
|
|
||||||
if (pid2 < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pid2 > 0) {
|
|
||||||
_exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
redirect_stdio_and_close();
|
|
||||||
#endif
|
|
||||||
#else
|
|
||||||
(void) use_gui;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
int
|
int
|
||||||
main(int argc, const char *argv[])
|
main(int argc, const char *argv[])
|
||||||
{
|
{
|
||||||
@@ -157,7 +56,7 @@ main(int argc, const char *argv[])
|
|||||||
|
|
||||||
int opt;
|
int opt;
|
||||||
int long_index = 0;
|
int long_index = 0;
|
||||||
while ((opt = getopt_long(argc, const_cast<char * const*>(argv), "gthV", long_opts, &long_index)) != -1) {
|
while ((opt = getopt_long(argc, const_cast<char *const *>(argv), "gthV", long_opts, &long_index)) != -1) {
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 'g':
|
case 'g':
|
||||||
req_gui = true;
|
req_gui = true;
|
||||||
@@ -212,8 +111,6 @@ main(int argc, const char *argv[])
|
|||||||
use_gui = false;
|
use_gui = false;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
// If using GUI, detach from the controlling terminal so the terminal can be closed.
|
|
||||||
DetachFromTerminalIfGUI(use_gui);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Open files passed on the CLI; support +N to jump to line N in the next file.
|
// Open files passed on the CLI; support +N to jump to line N in the next file.
|
||||||
@@ -293,6 +190,15 @@ main(int argc, const char *argv[])
|
|||||||
fe = std::make_unique<TerminalFrontend>();
|
fe = std::make_unique<TerminalFrontend>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(KTE_BUILD_GUI) && defined(__APPLE__)
|
||||||
|
if (use_gui) {
|
||||||
|
/* likely using the .app, so need to cd */
|
||||||
|
if (chdir(getenv("HOME")) != 0) {
|
||||||
|
std::cerr << "kge.app: failed to chdir to HOME" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!fe->Init(editor)) {
|
if (!fe->Init(editor)) {
|
||||||
std::cerr << "kte: failed to initialize frontend" << std::endl;
|
std::cerr << "kte: failed to initialize frontend" << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
#include <iostream>
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "Buffer.h"
|
||||||
|
#include "Command.h"
|
||||||
#include "Editor.h"
|
#include "Editor.h"
|
||||||
#include "TestFrontend.h"
|
#include "TestFrontend.h"
|
||||||
#include "Command.h"
|
|
||||||
#include "Buffer.h"
|
|
||||||
|
|
||||||
|
|
||||||
int
|
int
|
||||||
|
|||||||