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.
This commit is contained in:
16
.idea/codeStyles/Project.xml
generated
16
.idea/codeStyles/Project.xml
generated
@@ -17,7 +17,6 @@
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue" value="2" type="int" />
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_BLANK_LINES_IN_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/INDENT_CASE_FROM_SWITCH/@EntryValue" />
|
||||
<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/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/FUNCTION_DECLARATION_RETURN_TYPE_STYLE/@EntryValue" value="ON_SINGLE_LINE" type="string" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=0B82708A1BA7774EB13D27F245698A56/@EntryIndexedValue" value="<NamingElement Priority="1" Title="Classes and structs"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="__interface" /><type Name="class" /><type Name="struct" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=0B82708A1BA7774EB13D27F245698A56/@EntryIndexRemoved" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=A7EBF16DA3BDCB42A0B710704BC8A053/@EntryIndexedValue" value="<NamingElement Priority="3" Title="Enums"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="enum" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=A7EBF16DA3BDCB42A0B710704BC8A053/@EntryIndexRemoved" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=0AFB7787612DF743B09AD9412E48D4CC/@EntryIndexedValue" value="<NamingElement Priority="7" Title="Local variables"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="local variable" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></NamingElement>" type="string" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=0AFB7787612DF743B09AD9412E48D4CC/@EntryIndexRemoved" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=72514D5DF422D442B71A277F97B72887/@EntryIndexedValue" value="<NamingElement Priority="8" Title="Global variables"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="global variable" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AA_BB" /></NamingElement>" type="string" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=72514D5DF422D442B71A277F97B72887/@EntryIndexRemoved" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=821F3C5CF47D5640AD3511BCBADE17C4/@EntryIndexedValue" value="<NamingElement Priority="9" Title="Lambdas"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="lambda" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=821F3C5CF47D5640AD3511BCBADE17C4/@EntryIndexRemoved" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=8F69F48E2532F54CBAA0039D4557825E/@EntryIndexedValue" value="<NamingElement Priority="10" Title="Global functions"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="global function" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=8F69F48E2532F54CBAA0039D4557825E/@EntryIndexRemoved" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=B6E900853D6D05429D8C57765B2E546A/@EntryIndexedValue" value="<NamingElement Priority="11" Title="Class and struct methods"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="member function" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=B6E900853D6D05429D8C57765B2E546A/@EntryIndexRemoved" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=B82A063F0DDD98498A70D8D7EBB97F8D/@EntryIndexedValue" value="<NamingElement Priority="12" Title="Class and struct fields"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="class field" /><type Name="struct field" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="_" Style="aaBb" /></NamingElement>" type="string" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=B82A063F0DDD98498A70D8D7EBB97F8D/@EntryIndexRemoved" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=BBE8AA08E662BF409B2CB08EC597C493/@EntryIndexedValue" value="<NamingElement Priority="13" Title="Class and struct public fields"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="PUBLIC"><type Name="class field" /><type Name="struct field" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></NamingElement>" type="string" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=BBE8AA08E662BF409B2CB08EC597C493/@EntryIndexRemoved" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=A4FAA2257682A94F8C2C93E123FAFC7A/@EntryIndexedValue" value="<NamingElement Priority="19" Title="Typedefs"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="type alias" /><type Name="typedef" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=A4FAA2257682A94F8C2C93E123FAFC7A/@EntryIndexRemoved" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=BF0D1AE66D64FE4FAF613448A12051A0/@EntryIndexedValue" value="<NamingElement Priority="17" Title="Global constants"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="True" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="global variable" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=BF0D1AE66D64FE4FAF613448A12051A0/@EntryIndexRemoved" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=2B232F1067F0324F8FF4B9D63ACECDB2/@EntryIndexedValue" value="<NamingElement Priority="16" Title="Other constants"><Descriptor Static="True" Constexpr="Indeterminate" Const="True" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="class field" /><type Name="local variable" /><type Name="struct field" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=2B232F1067F0324F8FF4B9D63ACECDB2/@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/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" />
|
||||
</RiderCodeStyleSettings>
|
||||
<files>
|
||||
|
||||
114
.idea/workspace.xml
generated
114
.idea/workspace.xml
generated
@@ -21,7 +21,11 @@
|
||||
<option name="reloaded" value="true" />
|
||||
</component>
|
||||
<component name="CMakeRunConfigurationManager">
|
||||
<generated />
|
||||
<generated>
|
||||
<config projectName="kte" targetName="kte" />
|
||||
<config projectName="kte" targetName="imgui" />
|
||||
<config projectName="kte" targetName="kge" />
|
||||
</generated>
|
||||
</component>
|
||||
<component name="CMakeSettings" AUTO_RELOAD="true">
|
||||
<configurations>
|
||||
@@ -30,13 +34,17 @@
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="e1fe3ab0-3650-4fca-8664-a247d5dfa457" name="Changes" comment="Add GUI initialization updates and improve navigation commands. - Implement terminal detachment for GUI mode to enable terminal closure post-launch. - Add `+N` support for opening files at specific line numbers and refine cursor positioning. - Introduce `JumpToLine` command for direct navigation by line number. - Enhance mouse wheel handling for line-wise scrolling.">
|
||||
<change afterPath="$PROJECT_DIR$/default-nogui.nix" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/codeStyles/Project.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/codeStyles/Project.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$/default-gui.nix" beforeDir="false" afterPath="$PROJECT_DIR$/default-gui.nix" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/default.nix" beforeDir="false" afterPath="$PROJECT_DIR$/default.nix" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/flake.nix" beforeDir="false" afterPath="$PROJECT_DIR$/flake.nix" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/main.cc" beforeDir="false" afterPath="$PROJECT_DIR$/main.cc" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.junie/guidelines.md" beforeDir="false" afterPath="$PROJECT_DIR$/.junie/guidelines.md" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Buffer.cc" beforeDir="false" afterPath="$PROJECT_DIR$/Buffer.cc" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Command.cc" beforeDir="false" afterPath="$PROJECT_DIR$/Command.cc" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Command.h" beforeDir="false" afterPath="$PROJECT_DIR$/Command.h" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Editor.cc" beforeDir="false" afterPath="$PROJECT_DIR$/Editor.cc" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Editor.h" beforeDir="false" afterPath="$PROJECT_DIR$/Editor.h" 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$/TerminalRenderer.cc" beforeDir="false" afterPath="$PROJECT_DIR$/TerminalRenderer.cc" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -89,54 +97,69 @@
|
||||
<option name="sortByType" value="true" />
|
||||
<option name="sortKey" value="BY_TYPE" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"CMake Application.kge.executor": "Run",
|
||||
"CMake Application.test_example.executor": "Run",
|
||||
"CMake Application.test_undo.executor": "Run",
|
||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||
"NIXITCH_NIXPKGS_CONFIG": "",
|
||||
"NIXITCH_NIX_CONF_DIR": "",
|
||||
"NIXITCH_NIX_OTHER_STORES": "",
|
||||
"NIXITCH_NIX_PATH": "",
|
||||
"NIXITCH_NIX_PROFILES": "",
|
||||
"NIXITCH_NIX_REMOTE": "",
|
||||
"NIXITCH_NIX_USER_PROFILE_DIR": "",
|
||||
"RunOnceActivity.RadMigrateCodeStyle": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.cidr.known.project.marker": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"RunOnceActivity.readMode.enableVisualFormatting": "true",
|
||||
"RunOnceActivity.west.config.association.type.startup.service": "true",
|
||||
"cf.first.check.clang-format": "false",
|
||||
"cidr.known.project.marker": "true",
|
||||
"code.cleanup.on.save": "true",
|
||||
"com.intellij.ml.llm.matterhorn.ej.ui.settings.DefaultModelSelectionForGA.v1": "true",
|
||||
"git-widget-placeholder": "master",
|
||||
"junie.onboarding.icon.badge.shown": "true",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"onboarding.tips.debug.path": "/Users/kyle/src/kte/main.cpp",
|
||||
"rearrange.code.on.save": "true",
|
||||
"settings.editor.selected.configurable": "editor.preferences.fonts.default",
|
||||
"to.speed.mode.migration.done": "true",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
<component name="PropertiesComponent">{
|
||||
"keyToString": {
|
||||
"CMake Application.kge.executor": "Run",
|
||||
"CMake Application.test_example.executor": "Run",
|
||||
"CMake Application.test_undo.executor": "Run",
|
||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||
"NIXITCH_NIXPKGS_CONFIG": "",
|
||||
"NIXITCH_NIX_CONF_DIR": "",
|
||||
"NIXITCH_NIX_OTHER_STORES": "",
|
||||
"NIXITCH_NIX_PATH": "",
|
||||
"NIXITCH_NIX_PROFILES": "",
|
||||
"NIXITCH_NIX_REMOTE": "",
|
||||
"NIXITCH_NIX_USER_PROFILE_DIR": "",
|
||||
"RunOnceActivity.RadMigrateCodeStyle": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.cidr.known.project.marker": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"RunOnceActivity.readMode.enableVisualFormatting": "true",
|
||||
"RunOnceActivity.west.config.association.type.startup.service": "true",
|
||||
"cf.first.check.clang-format": "false",
|
||||
"cidr.known.project.marker": "true",
|
||||
"code.cleanup.on.save": "true",
|
||||
"com.intellij.ml.llm.matterhorn.ej.ui.settings.DefaultModelSelectionForGA.v1": "true",
|
||||
"git-widget-placeholder": "master",
|
||||
"junie.onboarding.icon.badge.shown": "true",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"onboarding.tips.debug.path": "/Users/kyle/src/kte/main.cpp",
|
||||
"rearrange.code.on.save": "true",
|
||||
"settings.editor.selected.configurable": "editor.preferences.fonts.default",
|
||||
"to.speed.mode.migration.done": "true",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
}
|
||||
}]]></component>
|
||||
}</component>
|
||||
<component name="RecentsManager">
|
||||
<key name="MoveFile.RECENT_KEYS">
|
||||
<recent name="$PROJECT_DIR$/docs" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="RunManager">
|
||||
<configuration default="true" type="CMakeRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true">
|
||||
<component name="RunManager" selected="CMake Application.kge">
|
||||
<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">
|
||||
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</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">
|
||||
<method v="2">
|
||||
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<configuration name="kte" 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="kte" CONFIG_NAME="Debug" RUN_TARGET_PROJECT_NAME="kte" RUN_TARGET_NAME="kte">
|
||||
<method v="2">
|
||||
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<list>
|
||||
<item itemvalue="CMake Application.imgui" />
|
||||
<item itemvalue="CMake Application.kge" />
|
||||
<item itemvalue="CMake Application.kte" />
|
||||
</list>
|
||||
</component>
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
@@ -153,6 +176,7 @@
|
||||
<workItem from="1764539556448" duration="156000" />
|
||||
<workItem from="1764539725338" duration="1075000" />
|
||||
<workItem from="1764542392763" duration="3512000" />
|
||||
<workItem from="1764548345516" duration="1529000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="Add undo/redo infrastructure and buffer management additions.">
|
||||
<option name="closed" value="true" />
|
||||
|
||||
@@ -1,39 +1,19 @@
|
||||
# Project Guidelines
|
||||
|
||||
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
|
||||
WordStar/VDE family and emacs. The spiritual parent is `mg(1)`.
|
||||
|
||||
These guidelines summarize the goals, interfaces, key operations, and current
|
||||
development practices for kte.
|
||||
|
||||
Style note: all code should be formatted with the current CLion C++ style.
|
||||
|
||||
## Goals
|
||||
|
||||
- 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.
|
||||
- Favor simple data structures (e.g., gap buffer) 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]`
|
||||
- Favor simple data structures (e.g., piece table) and incremental evolution.
|
||||
|
||||
Project entry point: `main.cpp`
|
||||
|
||||
@@ -52,31 +32,12 @@ Project entry point: `main.cpp`
|
||||
|
||||
## Keybindings (inherited from ke)
|
||||
|
||||
kte aims to maintain ke’s command model while internals evolve. See `ke.md` for
|
||||
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).
|
||||
The file `docs/ke.md` contains the canonical reference for keybindings.
|
||||
|
||||
## Contributing/Development Notes
|
||||
|
||||
- C++ standard: C++17.
|
||||
- Style: match existing file formatting and minimal-comment style.
|
||||
- Keep dependencies minimal; ImGui integration will be isolated behind a GUI
|
||||
module.
|
||||
- Keep dependencies minimal.
|
||||
- Prefer small, focused changes that preserve ke’s UX unless explicitly changing
|
||||
behavior.
|
||||
|
||||
|
||||
60
Buffer.cc
60
Buffer.cc
@@ -5,6 +5,7 @@
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <filesystem>
|
||||
#include <cstdlib>
|
||||
|
||||
|
||||
Buffer::Buffer()
|
||||
@@ -129,12 +130,36 @@ Buffer::operator=(Buffer &&other) noexcept
|
||||
bool
|
||||
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
|
||||
// with the provided filename. Do not touch the filesystem until Save/SaveAs.
|
||||
if (!std::filesystem::exists(path)) {
|
||||
if (!std::filesystem::exists(norm)) {
|
||||
rows_.clear();
|
||||
nrows_ = 0;
|
||||
filename_ = path;
|
||||
filename_ = norm;
|
||||
is_file_backed_ = false;
|
||||
dirty_ = false;
|
||||
|
||||
@@ -147,9 +172,9 @@ Buffer::OpenFromFile(const std::string &path, std::string &err)
|
||||
return true;
|
||||
}
|
||||
|
||||
std::ifstream in(path, std::ios::in | std::ios::binary);
|
||||
std::ifstream in(norm, std::ios::in | std::ios::binary);
|
||||
if (!in) {
|
||||
err = "Failed to open file: " + path;
|
||||
err = "Failed to open file: " + norm;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -194,7 +219,7 @@ Buffer::OpenFromFile(const std::string &path, std::string &err)
|
||||
}
|
||||
|
||||
nrows_ = rows_.size();
|
||||
filename_ = path;
|
||||
filename_ = norm;
|
||||
is_file_backed_ = true;
|
||||
dirty_ = false;
|
||||
|
||||
@@ -250,10 +275,29 @@ Buffer::Save(std::string &err) const
|
||||
bool
|
||||
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
|
||||
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) {
|
||||
err = "Failed to open for write: " + path;
|
||||
err = "Failed to open for write: " + out_path;
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
filename_ = path;
|
||||
filename_ = out_path;
|
||||
is_file_backed_ = true;
|
||||
dirty_ = false;
|
||||
return true;
|
||||
|
||||
268
Command.cc
268
Command.cc
@@ -1,5 +1,6 @@
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "Command.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
|
||||
cmd_save_as(CommandContext &ctx)
|
||||
{
|
||||
@@ -762,43 +793,141 @@ cmd_insert_text(CommandContext &ctx)
|
||||
}
|
||||
// If a prompt is active, edit prompt text
|
||||
if (ctx.editor.PromptActive()) {
|
||||
// Special-case: buffer switch prompt supports Tab-completion
|
||||
if (ctx.editor.CurrentPromptKind() == Editor::PromptKind::BufferSwitch && ctx.arg == "\t") {
|
||||
// Complete against buffer names (path and basename)
|
||||
const std::string prefix = ctx.editor.PromptText();
|
||||
std::vector<std::pair<std::string, std::size_t> > cands; // name, index
|
||||
const auto &bs = ctx.editor.Buffers();
|
||||
for (std::size_t i = 0; i < bs.size(); ++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);
|
||||
// Special-case: Tab-completion for prompts
|
||||
if (ctx.arg == "\t") {
|
||||
auto kind = ctx.editor.CurrentPromptKind();
|
||||
// Buffer switch prompt supports Tab-completion on buffer names
|
||||
if (kind == Editor::PromptKind::BufferSwitch) {
|
||||
// Complete against buffer names (path and basename)
|
||||
const std::string prefix = ctx.editor.PromptText();
|
||||
std::vector<std::pair<std::string, std::size_t> > cands; // name, index
|
||||
const auto &bs = ctx.editor.Buffers();
|
||||
for (std::size_t i = 0; i < bs.size(); ++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) {
|
||||
cands.emplace_back(base, i);
|
||||
if (cands.empty()) {
|
||||
// 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
|
||||
} 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;
|
||||
|
||||
// File path completion for OpenFile/SaveAs/Chdir
|
||||
if (kind == Editor::PromptKind::OpenFile || kind == Editor::PromptKind::SaveAs
|
||||
|| kind == Editor::PromptKind::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); // keep leading slash
|
||||
return std::string(home) + rest;
|
||||
}
|
||||
}
|
||||
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);
|
||||
@@ -909,6 +1038,20 @@ cmd_newline(CommandContext &ctx)
|
||||
ensure_cursor_visible(ctx.editor, *b);
|
||||
} else if (kind == Editor::PromptKind::OpenFile) {
|
||||
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()) {
|
||||
ctx.editor.SetStatus("Open canceled (empty)");
|
||||
} else if (!ctx.editor.OpenFile(value, err)) {
|
||||
@@ -953,6 +1096,21 @@ cmd_newline(CommandContext &ctx)
|
||||
if (!buf) {
|
||||
ctx.editor.SetStatus("No buffer to save");
|
||||
} 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
|
||||
// target exists, ask for confirmation before overwriting.
|
||||
if (!buf->IsFileBacked() && std::filesystem::exists(value)) {
|
||||
@@ -1031,6 +1189,43 @@ cmd_newline(CommandContext &ctx)
|
||||
buf->SetCursor(0, y);
|
||||
ensure_cursor_visible(ctx.editor, *buf);
|
||||
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;
|
||||
}
|
||||
@@ -2435,6 +2630,15 @@ InstallDefaultCommands()
|
||||
CommandId::MarkAllAndJumpEnd, "mark-all-jump-end", "Set mark at beginning and jump to 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
|
||||
CommandRegistry::Register(
|
||||
{CommandId::UArgStatus, "uarg-status", "Update universal-arg status", cmd_uarg_status});
|
||||
|
||||
@@ -74,6 +74,8 @@ enum class CommandId {
|
||||
MarkAllAndJumpEnd, // set mark at beginning, jump to end (C-k a)
|
||||
// Direct navigation by line number
|
||||
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
|
||||
UnknownKCommand, // arg: single character that was not recognized after C-k
|
||||
};
|
||||
|
||||
73
Editor.cc
73
Editor.cc
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <filesystem>
|
||||
|
||||
|
||||
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
|
||||
Editor::AddBuffer(const Buffer &buf)
|
||||
{
|
||||
|
||||
7
Editor.h
7
Editor.h
@@ -302,7 +302,7 @@ public:
|
||||
|
||||
|
||||
// --- 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)
|
||||
@@ -409,6 +409,11 @@ public:
|
||||
|
||||
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
|
||||
std::size_t AddBuffer(const Buffer &buf);
|
||||
|
||||
|
||||
@@ -204,18 +204,26 @@ GUIRenderer::Draw(Editor &ed)
|
||||
left += "kge"; // GUI app name
|
||||
left += " ";
|
||||
left += KTE_VERSION_STR;
|
||||
std::string fname = buf->Filename();
|
||||
if (!fname.empty()) {
|
||||
std::string fname;
|
||||
try {
|
||||
fname = ed.DisplayNameFor(*buf);
|
||||
} catch (...) {
|
||||
fname = buf->Filename();
|
||||
try {
|
||||
fname = std::filesystem::path(fname).filename().string();
|
||||
} catch (...) {}
|
||||
} else {
|
||||
fname = "[no name]";
|
||||
}
|
||||
left += " ";
|
||||
left += fname;
|
||||
if (buf->Dirty())
|
||||
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)
|
||||
int row1 = static_cast<int>(buf->Cury()) + 1;
|
||||
|
||||
119
KKeymap.cc
119
KKeymap.cc
@@ -13,13 +13,13 @@ KLookupKCommand(const int ascii_key, const bool ctrl, CommandId &out) -> bool
|
||||
switch (k_lower) {
|
||||
case 'd':
|
||||
out = CommandId::KillLine;
|
||||
return true; // C-k C-d
|
||||
case 'x':
|
||||
out = CommandId::SaveAndQuit;
|
||||
return true; // C-k C-x
|
||||
return true;
|
||||
case 'q':
|
||||
out = CommandId::QuitNow;
|
||||
return true; // C-k C-q (quit immediately)
|
||||
return true;
|
||||
case 'x':
|
||||
out = CommandId::SaveAndQuit;
|
||||
return true;
|
||||
default:
|
||||
// Important: do not return here — fall through to non-ctrl table
|
||||
// so that C-k u/U still work even if Ctrl is (incorrectly) held
|
||||
@@ -33,65 +33,72 @@ KLookupKCommand(const int ascii_key, const bool ctrl, CommandId &out) -> bool
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3) Non-control k-table (lowercased)
|
||||
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':
|
||||
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 'f':
|
||||
out = CommandId::FlushKillRing;
|
||||
return true;
|
||||
case 'g':
|
||||
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:
|
||||
break;
|
||||
}
|
||||
|
||||
// 3) Non-control k-table (lowercased)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -168,13 +168,13 @@ TerminalRenderer::Draw(Editor &ed)
|
||||
const Buffer *b = buf;
|
||||
std::string fname;
|
||||
if (b) {
|
||||
fname = b->Filename();
|
||||
}
|
||||
if (!fname.empty()) {
|
||||
try {
|
||||
fname = std::filesystem::path(fname).filename().string();
|
||||
fname = ed.DisplayNameFor(*b);
|
||||
} catch (...) {
|
||||
// keep original on any error
|
||||
fname = b->Filename();
|
||||
try {
|
||||
fname = std::filesystem::path(fname).filename().string();
|
||||
} catch (...) {}
|
||||
}
|
||||
} else {
|
||||
fname = "[no name]";
|
||||
@@ -183,6 +183,13 @@ TerminalRenderer::Draw(Editor &ed)
|
||||
left += fname;
|
||||
if (b && b->Dirty())
|
||||
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)
|
||||
|
||||
67
main.cc
67
main.cc
@@ -26,52 +26,53 @@ static void
|
||||
PrintUsage(const char *prog)
|
||||
{
|
||||
std::cerr << "Usage: " << prog << " [OPTIONS] [files]\n"
|
||||
<< "Options:\n"
|
||||
<< " -g, --gui Use GUI frontend (if built)\n"
|
||||
<< " -t, --term Use terminal (ncurses) frontend [default]\n"
|
||||
<< " -h, --help Show this help and exit\n"
|
||||
<< " -V, --version Show version and exit\n";
|
||||
<< "Options:\n"
|
||||
<< " -g, --gui Use GUI frontend (if built)\n"
|
||||
<< " -t, --term Use terminal (ncurses) frontend [default]\n"
|
||||
<< " -h, --help Show this help and exit\n"
|
||||
<< " -V, --version Show version and exit\n";
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
main(int argc, const char *argv[])
|
||||
{
|
||||
Editor editor;
|
||||
|
||||
// CLI parsing using getopt_long
|
||||
bool req_gui = false;
|
||||
bool req_term = false;
|
||||
bool show_help = false;
|
||||
bool req_gui = false;
|
||||
bool req_term = false;
|
||||
bool show_help = false;
|
||||
bool show_version = false;
|
||||
|
||||
static struct option long_opts[] = {
|
||||
{"gui", no_argument, nullptr, 'g'},
|
||||
{"term", no_argument, nullptr, 't'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"version", no_argument, nullptr, 'V'},
|
||||
{nullptr, 0, nullptr, 0}
|
||||
{"gui", no_argument, nullptr, 'g'},
|
||||
{"term", no_argument, nullptr, 't'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"version", no_argument, nullptr, 'V'},
|
||||
{nullptr, 0, nullptr, 0}
|
||||
};
|
||||
|
||||
int opt;
|
||||
int long_index = 0;
|
||||
while ((opt = getopt_long(argc, const_cast<char *const *>(argv), "gthV", long_opts, &long_index)) != -1) {
|
||||
switch (opt) {
|
||||
case 'g':
|
||||
req_gui = true;
|
||||
break;
|
||||
case 't':
|
||||
req_term = true;
|
||||
break;
|
||||
case 'h':
|
||||
show_help = true;
|
||||
break;
|
||||
case 'V':
|
||||
show_version = true;
|
||||
break;
|
||||
case '?':
|
||||
default:
|
||||
PrintUsage(argv[0]);
|
||||
return 2;
|
||||
case 'g':
|
||||
req_gui = true;
|
||||
break;
|
||||
case 't':
|
||||
req_term = true;
|
||||
break;
|
||||
case 'h':
|
||||
show_help = true;
|
||||
break;
|
||||
case 'V':
|
||||
show_version = true;
|
||||
break;
|
||||
case '?':
|
||||
default:
|
||||
PrintUsage(argv[0]);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +93,7 @@ main(int argc, const char *argv[])
|
||||
#if !defined(KTE_BUILD_GUI)
|
||||
if (req_gui) {
|
||||
std::cerr << "kte: GUI not built. Reconfigure with -DBUILD_GUI=ON and required deps installed." <<
|
||||
std::endl;
|
||||
std::endl;
|
||||
return 2;
|
||||
}
|
||||
#else
|
||||
@@ -132,7 +133,7 @@ main(int argc, const char *argv[])
|
||||
// Clamp to >=1 later; 0 disables.
|
||||
try {
|
||||
unsigned long v = std::stoul(p);
|
||||
pending_line = static_cast<std::size_t>(v);
|
||||
pending_line = static_cast<std::size_t>(v);
|
||||
} catch (...) {
|
||||
// Ignore malformed huge numbers
|
||||
pending_line = 0;
|
||||
@@ -152,7 +153,7 @@ main(int argc, const char *argv[])
|
||||
// Apply pending +N to the just-opened (current) buffer
|
||||
if (Buffer *b = editor.CurrentBuffer()) {
|
||||
std::size_t nrows = b->Nrows();
|
||||
std::size_t line = pending_line > 0 ? pending_line - 1 : 0;
|
||||
std::size_t line = pending_line > 0 ? pending_line - 1 : 0;
|
||||
// 1-based to 0-based
|
||||
if (nrows > 0) {
|
||||
if (line >= nrows)
|
||||
@@ -178,7 +179,7 @@ main(int argc, const char *argv[])
|
||||
InstallDefaultCommands();
|
||||
|
||||
// Select frontend
|
||||
std::unique_ptr <Frontend> fe;
|
||||
std::unique_ptr<Frontend> fe;
|
||||
#if defined(KTE_BUILD_GUI)
|
||||
if (use_gui) {
|
||||
fe = std::make_unique<GUIFrontend>();
|
||||
|
||||
Reference in New Issue
Block a user