8 Commits

Author SHA1 Message Date
ba9bd4a27d Bump version.
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
2025-11-30 17:22:54 -08:00
fcb2e9a7ed 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.
2025-11-30 17:19:46 -08:00
38ba8c9871 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.
2025-11-30 16:49:24 -08:00
b91406860c update nix build
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
2025-11-30 16:07:30 -08:00
8d1e9b2799 default to using the piece table 2025-11-30 10:35:28 -08:00
c91fe214d6 building nix 2025-11-30 04:46:10 -08:00
99042f5ef1 update nix build 2025-11-30 04:42:21 -08:00
96242154f7 build non-gui by default 2025-11-30 04:34:15 -08:00
19 changed files with 909 additions and 494 deletions

View File

@@ -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="&lt;NamingElement Priority=&quot;1&quot; Title=&quot;Classes and structs&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;__interface&quot; /&gt;&lt;type Name=&quot;class&quot; /&gt;&lt;type Name=&quot;struct&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" /> <option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=0B82708A1BA7774EB13D27F245698A56/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;1&quot; Title=&quot;Classes and structs&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;__interface&quot; /&gt;&lt;type Name=&quot;class&quot; /&gt;&lt;type Name=&quot;struct&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=0B82708A1BA7774EB13D27F245698A56/@EntryIndexRemoved" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=A7EBF16DA3BDCB42A0B710704BC8A053/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;3&quot; Title=&quot;Enums&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;enum&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" /> <option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=A7EBF16DA3BDCB42A0B710704BC8A053/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;3&quot; Title=&quot;Enums&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;enum&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=A7EBF16DA3BDCB42A0B710704BC8A053/@EntryIndexRemoved" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=0AFB7787612DF743B09AD9412E48D4CC/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;7&quot; Title=&quot;Local variables&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;local variable&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;aaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" /> <option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=0AFB7787612DF743B09AD9412E48D4CC/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;7&quot; Title=&quot;Local variables&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;local variable&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;aaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=0AFB7787612DF743B09AD9412E48D4CC/@EntryIndexRemoved" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=72514D5DF422D442B71A277F97B72887/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;8&quot; Title=&quot;Global variables&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;global variable&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AA_BB&quot; /&gt;&lt;/NamingElement&gt;" type="string" /> <option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=72514D5DF422D442B71A277F97B72887/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;8&quot; Title=&quot;Global variables&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;global variable&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AA_BB&quot; /&gt;&lt;/NamingElement&gt;" type="string" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=72514D5DF422D442B71A277F97B72887/@EntryIndexRemoved" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=821F3C5CF47D5640AD3511BCBADE17C4/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;9&quot; Title=&quot;Lambdas&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;lambda&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" /> <option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=821F3C5CF47D5640AD3511BCBADE17C4/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;9&quot; Title=&quot;Lambdas&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;lambda&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=821F3C5CF47D5640AD3511BCBADE17C4/@EntryIndexRemoved" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=8F69F48E2532F54CBAA0039D4557825E/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;10&quot; Title=&quot;Global functions&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;global function&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" /> <option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=8F69F48E2532F54CBAA0039D4557825E/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;10&quot; Title=&quot;Global functions&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;global function&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=8F69F48E2532F54CBAA0039D4557825E/@EntryIndexRemoved" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=B6E900853D6D05429D8C57765B2E546A/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;11&quot; Title=&quot;Class and struct methods&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;member function&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" /> <option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=B6E900853D6D05429D8C57765B2E546A/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;11&quot; Title=&quot;Class and struct methods&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;member function&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=B6E900853D6D05429D8C57765B2E546A/@EntryIndexRemoved" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=B82A063F0DDD98498A70D8D7EBB97F8D/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;12&quot; Title=&quot;Class and struct fields&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;class field&quot; /&gt;&lt;type Name=&quot;struct field&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;_&quot; Style=&quot;aaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" /> <option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=B82A063F0DDD98498A70D8D7EBB97F8D/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;12&quot; Title=&quot;Class and struct fields&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;class field&quot; /&gt;&lt;type Name=&quot;struct field&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;_&quot; Style=&quot;aaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=B82A063F0DDD98498A70D8D7EBB97F8D/@EntryIndexRemoved" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=BBE8AA08E662BF409B2CB08EC597C493/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;13&quot; Title=&quot;Class and struct public fields&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;PUBLIC&quot;&gt;&lt;type Name=&quot;class field&quot; /&gt;&lt;type Name=&quot;struct field&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;aaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" /> <option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=BBE8AA08E662BF409B2CB08EC597C493/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;13&quot; Title=&quot;Class and struct public fields&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;PUBLIC&quot;&gt;&lt;type Name=&quot;class field&quot; /&gt;&lt;type Name=&quot;struct field&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;aaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=BBE8AA08E662BF409B2CB08EC597C493/@EntryIndexRemoved" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=A4FAA2257682A94F8C2C93E123FAFC7A/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;19&quot; Title=&quot;Typedefs&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;type alias&quot; /&gt;&lt;type Name=&quot;typedef&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" /> <option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=A4FAA2257682A94F8C2C93E123FAFC7A/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;19&quot; Title=&quot;Typedefs&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;type alias&quot; /&gt;&lt;type Name=&quot;typedef&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=A4FAA2257682A94F8C2C93E123FAFC7A/@EntryIndexRemoved" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=BF0D1AE66D64FE4FAF613448A12051A0/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;17&quot; Title=&quot;Global constants&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;True&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;global variable&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" /> <option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=BF0D1AE66D64FE4FAF613448A12051A0/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;17&quot; Title=&quot;Global constants&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;True&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;global variable&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=BF0D1AE66D64FE4FAF613448A12051A0/@EntryIndexRemoved" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=2B232F1067F0324F8FF4B9D63ACECDB2/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;16&quot; Title=&quot;Other constants&quot;&gt;&lt;Descriptor Static=&quot;True&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;True&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;class field&quot; /&gt;&lt;type Name=&quot;local variable&quot; /&gt;&lt;type Name=&quot;struct field&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" /> <option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=2B232F1067F0324F8FF4B9D63ACECDB2/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;16&quot; Title=&quot;Other constants&quot;&gt;&lt;Descriptor Static=&quot;True&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;True&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;class field&quot; /&gt;&lt;type Name=&quot;local variable&quot; /&gt;&lt;type Name=&quot;struct field&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AaBb&quot; /&gt;&lt;/NamingElement&gt;" 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>

118
.idea/workspace.xml generated
View File

@@ -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 &quot;useNewFormat&quot;: true
}]]></component> }</component>
<component name="CMakeProjectFlavorService"> <component name="CMakeProjectFlavorService">
<option name="flavorId" value="CMakePlainProjectFlavor" /> <option name="flavorId" value="CMakePlainProjectFlavor" />
</component> </component>
@@ -33,9 +33,12 @@
</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.&#10;&#10;- Implement terminal detachment for GUI mode to enable terminal closure post-launch.&#10;- Add `+N` support for opening files at specific line numbers and refine cursor positioning.&#10;- Introduce `JumpToLine` command for direct navigation by line number.&#10;- Enhance mouse wheel handling for line-wise scrolling."> <list default="true" id="e1fe3ab0-3650-4fca-8664-a247d5dfa457" name="Changes" comment="Refactor code for consistency and enhanced functionality.&#10;&#10;- Normalize path handling for buffer operations, supporting tilde expansion and absolute paths.&#10;- Introduce `DisplayNameFor` to uniquely resolve buffer display names, minimizing filename clashes.&#10;- Add new commands: `ShowWorkingDirectory` and `ChangeWorkingDirectory`.&#10;- Refine keybindings and enhance existing commands for improved command flow.&#10;- Adjust GUI and terminal renderers to display total line counts alongside filenames.&#10;- Update coding style to align with project guidelines.">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <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$/Buffer.cc" beforeDir="false" afterPath="$PROJECT_DIR$/Buffer.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/GUIRenderer.cc" beforeDir="false" afterPath="$PROJECT_DIR$/GUIRenderer.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/KKeymap.cc" beforeDir="false" afterPath="$PROJECT_DIR$/KKeymap.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/main.cc" beforeDir="false" afterPath="$PROJECT_DIR$/main.cc" 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" />
@@ -75,9 +78,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 &quot;associatedIndex&quot;: 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,59 +91,55 @@
<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": { &quot;keyToString&quot;: {
"CMake Application.kge.executor": "Run", &quot;CMake Application.kge.executor&quot;: &quot;Run&quot;,
"CMake Application.test_example.executor": "Run", &quot;CMake Application.test_example.executor&quot;: &quot;Run&quot;,
"CMake Application.test_undo.executor": "Run", &quot;CMake Application.test_undo.executor&quot;: &quot;Run&quot;,
"ModuleVcsDetector.initialDetectionPerformed": "true", &quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;,
"NIXITCH_NIXPKGS_CONFIG": "", &quot;NIXITCH_NIXPKGS_CONFIG&quot;: &quot;&quot;,
"NIXITCH_NIX_CONF_DIR": "", &quot;NIXITCH_NIX_CONF_DIR&quot;: &quot;&quot;,
"NIXITCH_NIX_OTHER_STORES": "", &quot;NIXITCH_NIX_OTHER_STORES&quot;: &quot;&quot;,
"NIXITCH_NIX_PATH": "", &quot;NIXITCH_NIX_PATH&quot;: &quot;&quot;,
"NIXITCH_NIX_PROFILES": "", &quot;NIXITCH_NIX_PROFILES&quot;: &quot;&quot;,
"NIXITCH_NIX_REMOTE": "", &quot;NIXITCH_NIX_REMOTE&quot;: &quot;&quot;,
"NIXITCH_NIX_USER_PROFILE_DIR": "", &quot;NIXITCH_NIX_USER_PROFILE_DIR&quot;: &quot;&quot;,
"RunOnceActivity.RadMigrateCodeStyle": "true", &quot;RunOnceActivity.RadMigrateCodeStyle&quot;: &quot;true&quot;,
"RunOnceActivity.ShowReadmeOnStart": "true", &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
"RunOnceActivity.cidr.known.project.marker": "true", &quot;RunOnceActivity.cidr.known.project.marker&quot;: &quot;true&quot;,
"RunOnceActivity.readMode.enableVisualFormatting": "true", &quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
"RunOnceActivity.west.config.association.type.startup.service": "true", &quot;RunOnceActivity.readMode.enableVisualFormatting&quot;: &quot;true&quot;,
"cf.first.check.clang-format": "false", &quot;RunOnceActivity.west.config.association.type.startup.service&quot;: &quot;true&quot;,
"cidr.known.project.marker": "true", &quot;cf.first.check.clang-format&quot;: &quot;false&quot;,
"code.cleanup.on.save": "true", &quot;cidr.known.project.marker&quot;: &quot;true&quot;,
"com.intellij.ml.llm.matterhorn.ej.ui.settings.DefaultModelSelectionForGA.v1": "true", &quot;code.cleanup.on.save&quot;: &quot;true&quot;,
"git-widget-placeholder": "master", &quot;com.intellij.ml.llm.matterhorn.ej.ui.settings.DefaultModelSelectionForGA.v1&quot;: &quot;true&quot;,
"junie.onboarding.icon.badge.shown": "true", &quot;git-widget-placeholder&quot;: &quot;master&quot;,
"node.js.detected.package.eslint": "true", &quot;junie.onboarding.icon.badge.shown&quot;: &quot;true&quot;,
"node.js.detected.package.tslint": "true", &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
"node.js.selected.package.eslint": "(autodetect)", &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
"node.js.selected.package.tslint": "(autodetect)", &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
"nodejs_package_manager_path": "npm", &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
"onboarding.tips.debug.path": "/Users/kyle/src/kte/main.cpp", &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
"rearrange.code.on.save": "true", &quot;onboarding.tips.debug.path&quot;: &quot;/Users/kyle/src/kte/main.cpp&quot;,
"settings.editor.selected.configurable": "junie.application.models", &quot;rearrange.code.on.save&quot;: &quot;true&quot;,
"to.speed.mode.migration.done": "true", &quot;settings.editor.selected.configurable&quot;: &quot;editor.preferences.fonts.default&quot;,
"vue.rearranger.settings.migration": "true" &quot;to.speed.mode.migration.done&quot;: &quot;true&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
} }
}]]></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">
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</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">
<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>
</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 +163,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="3453000" />
</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 +244,15 @@
<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.&#10;&#10;- Normalize path handling for buffer operations, supporting tilde expansion and absolute paths.&#10;- Introduce `DisplayNameFor` to uniquely resolve buffer display names, minimizing filename clashes.&#10;- Add new commands: `ShowWorkingDirectory` and `ChangeWorkingDirectory`.&#10;- Refine keybindings and enhance existing commands for improved command flow.&#10;- Adjust GUI and terminal renderers to display total line counts alongside filenames.&#10;- 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>
<option name="localTasksCounter" value="11" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
@@ -260,7 +275,8 @@
<MESSAGE value="Remove `packaging.cmake`, deprecate `test_undo` setup, and add new testing infrastructure.&#10;&#10;- Delete `packaging.cmake` to streamline build system.&#10;- Deprecate `test_undo` in CMake setup; condition builds on `BUILD_TESTS`.&#10;- Introduce `TestFrontend`, `TestRenderer`, and `TestInputHandler` for structured testing.&#10;- Update `GUIInputHandler` and `Command` for enhanced buffer save handling and overwrite confirmation.&#10;- Enhance kill ring operations and new prompt workflows in `Editor`." /> <MESSAGE value="Remove `packaging.cmake`, deprecate `test_undo` setup, and add new testing infrastructure.&#10;&#10;- Delete `packaging.cmake` to streamline build system.&#10;- Deprecate `test_undo` in CMake setup; condition builds on `BUILD_TESTS`.&#10;- Introduce `TestFrontend`, `TestRenderer`, and `TestInputHandler` for structured testing.&#10;- Update `GUIInputHandler` and `Command` for enhanced buffer save handling and overwrite confirmation.&#10;- Enhance kill ring operations and new prompt workflows in `Editor`." />
<MESSAGE value="Add man pages for `kge` and `kte` with installation targets in CMake.&#10;&#10;- Introduce `docs/kge.1` and `docs/kte.1` man pages covering usage, options, keybindings, and examples.&#10;- Update `CMakeLists.txt` to install man pages under `${CMAKE_INSTALL_MANDIR}/man1`.&#10;- 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.&#10;&#10;- Introduce `docs/kge.1` and `docs/kte.1` man pages covering usage, options, keybindings, and examples.&#10;- Update `CMakeLists.txt` to install man pages under `${CMAKE_INSTALL_MANDIR}/man1`.&#10;- Ensure `kge` man page installation is conditional on GUI being built." />
<MESSAGE value="Add GUI initialization updates and improve navigation commands.&#10;&#10;- Implement terminal detachment for GUI mode to enable terminal closure post-launch.&#10;- Add `+N` support for opening files at specific line numbers and refine cursor positioning.&#10;- Introduce `JumpToLine` command for direct navigation by line number.&#10;- Enhance mouse wheel handling for line-wise scrolling." /> <MESSAGE value="Add GUI initialization updates and improve navigation commands.&#10;&#10;- Implement terminal detachment for GUI mode to enable terminal closure post-launch.&#10;- Add `+N` support for opening files at specific line numbers and refine cursor positioning.&#10;- Introduce `JumpToLine` command for direct navigation by line number.&#10;- Enhance mouse wheel handling for line-wise scrolling." />
<option name="LAST_COMMIT_MESSAGE" value="Add GUI initialization updates and improve navigation commands.&#10;&#10;- Implement terminal detachment for GUI mode to enable terminal closure post-launch.&#10;- Add `+N` support for opening files at specific line numbers and refine cursor positioning.&#10;- Introduce `JumpToLine` command for direct navigation by line number.&#10;- Enhance mouse wheel handling for line-wise scrolling." /> <MESSAGE value="Refactor code for consistency and enhanced functionality.&#10;&#10;- Normalize path handling for buffer operations, supporting tilde expansion and absolute paths.&#10;- Introduce `DisplayNameFor` to uniquely resolve buffer display names, minimizing filename clashes.&#10;- Add new commands: `ShowWorkingDirectory` and `ChangeWorkingDirectory`.&#10;- Refine keybindings and enhance existing commands for improved command flow.&#10;- Adjust GUI and terminal renderers to display total line counts alongside filenames.&#10;- Update coding style to align with project guidelines." />
<option name="LAST_COMMIT_MESSAGE" value="Refactor code for consistency and enhanced functionality.&#10;&#10;- Normalize path handling for buffer operations, supporting tilde expansion and absolute paths.&#10;- Introduce `DisplayNameFor` to uniquely resolve buffer display names, minimizing filename clashes.&#10;- Add new commands: `ShowWorkingDirectory` and `ChangeWorkingDirectory`.&#10;- Refine keybindings and enhance existing commands for improved command flow.&#10;- Adjust GUI and terminal renderers to display total line counts alongside filenames.&#10;- Update coding style to align with project guidelines." />
</component> </component>
<component name="XSLT-Support.FileAssociations.UIState"> <component name="XSLT-Support.FileAssociations.UIState">
<expand /> <expand />

View File

@@ -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 kes 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 kes UX unless explicitly changing - Prefer small, focused changes that preserve kes UX unless explicitly changing
behavior. behavior.

103
Buffer.cc
View File

@@ -5,6 +5,7 @@
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <filesystem> #include <filesystem>
#include <cstdlib>
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);
} }

View File

@@ -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 "0.9.2")
# 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})

View File

@@ -1,5 +1,6 @@
#include <algorithm> #include <algorithm>
#include <filesystem> #include <filesystem>
#include <cstdlib>
#include "Command.h" #include "Command.h"
#include "Editor.h" #include "Editor.h"
@@ -453,6 +454,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)
{ {
@@ -762,43 +793,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 +1038,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 +1096,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 +1189,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 +2630,15 @@ 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
}); });
// 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});

View File

@@ -74,6 +74,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
}; };

View File

@@ -2,6 +2,7 @@
#include <algorithm> #include <algorithm>
#include <utility> #include <utility>
#include <filesystem>
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)
{ {

View File

@@ -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);

View File

@@ -8,6 +8,8 @@
#include <cstdio> #include <cstdio>
#include <string> #include <string>
#include <filesystem> #include <filesystem>
#include <cmath>
#include <limits>
// 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
@@ -63,30 +65,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 +137,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 +287,26 @@ 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 += " ";
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;

View File

@@ -1,5 +1,8 @@
#include "KKeymap.h" #include "KKeymap.h"
#include <iostream>
#include <ncurses.h> #include <ncurses.h>
#include <ostream>
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,75 @@ 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 '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 +148,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;
} }

View File

@@ -168,13 +168,13 @@ 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]";
@@ -183,6 +183,13 @@ TerminalRenderer::Draw(Editor &ed)
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)

51
default-gui.nix Normal file
View 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
View 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
'';
}

View File

@@ -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
'';
} }

55
flake-gui.nix Normal file
View 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
View File

@@ -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",

View File

@@ -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 ];
};
});
}

117
main.cc
View File

@@ -12,6 +12,7 @@
#include "Command.h" #include "Command.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
@@ -33,109 +34,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 +55,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 +110,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 +189,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;