{"id":31024926,"url":"https://github.com/iambelugax/goditor","last_synced_at":"2025-09-13T16:12:13.037Z","repository":{"id":303630269,"uuid":"1016161835","full_name":"iamBelugax/goditor","owner":"iamBelugax","description":"Text Editor with Undo/Redo Using the Command Pattern.","archived":false,"fork":false,"pushed_at":"2025-07-08T15:29:07.000Z","size":13,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-12T14:49:20.245Z","etag":null,"topics":["command","command-desing-pattern","design-patterns","golang","text-editor"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/iamBelugax.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-07-08T15:22:49.000Z","updated_at":"2025-07-08T15:29:12.000Z","dependencies_parsed_at":"2025-07-08T16:44:57.847Z","dependency_job_id":"77fdb3c9-8554-4dcb-992d-7c2ff8fb8691","html_url":"https://github.com/iamBelugax/goditor","commit_stats":null,"previous_names":["iambelugaa/goditor","iambelugax/goditor"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/iamBelugax/goditor","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamBelugax%2Fgoditor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamBelugax%2Fgoditor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamBelugax%2Fgoditor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamBelugax%2Fgoditor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/iamBelugax","download_url":"https://codeload.github.com/iamBelugax/goditor/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamBelugax%2Fgoditor/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274989932,"owners_count":25386552,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-13T02:00:10.085Z","response_time":70,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["command","command-desing-pattern","design-patterns","golang","text-editor"],"created_at":"2025-09-13T16:12:10.698Z","updated_at":"2025-09-13T16:12:13.015Z","avatar_url":"https://github.com/iamBelugax.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Text Editor with Undo/Redo Using the Command Pattern\n\nThis project demonstrates how to build a text editor that can undo and redo\noperations using the Command pattern.\n\n## Understanding the Command Pattern\n\nThe Command pattern works by wrapping each action in an object that knows how to\nperform that action. Instead of calling methods directly on your data, you\ncreate command objects that contain all the information needed to perform an\noperation.\n\nThink of it like writing instructions on index cards. Instead of immediately\ndoing something when someone asks, you write down exactly what they want on a\ncard, then follow the instructions on the card. This way, you have a stack of\ninstruction cards that you can go through one by one, skip some, or even go\nbackwards through them.\n\nIn our text editor, every time someone wants to insert text or delete text, we\ndon't change the text directly. Instead, we create a command object that knows\nexactly what change to make. Then we tell that command to execute itself. After\nit runs, we save both the command and the result, so we can see what happened\nand potentially go back to how things were before.\n\n## How the Internal Mechanics Work\n\nWhen you create a new text editor, it starts with an empty history and a current\nposition of negative one, which means there's no text yet. The history is like a\ntimeline where each point represents what the text looked like after some\noperation.\n\nLet's trace through what happens step by step when you perform operations. When\nyou call `Insert(0, \"Hello\")`, the editor first creates an InsertCommand object.\nThis object stores the position where text should be inserted (position 0) and\nthe text to insert (\"Hello\"). The editor then asks this command to execute\nitself by calling `Execute()` on the current text, which starts as an empty\nstring.\n\nThe InsertCommand's Execute method converts the current text into an array of\nUnicode characters (called runes in Go), finds the insertion point, and creates\na new string with the new text inserted at that position. Since we're inserting\nat position 0 in an empty string, the result is simply \"Hello\".\n\nThe editor takes this result and creates a new State object that contains both\nthe resulting text (\"Hello\") and the command that produced it. This state gets\nadded to the history array, and the current position moves to point at this new\nstate.\n\nNow when you call `Insert(5, \" World\")`, the same process happens, but this time\nthe InsertCommand executes against the current text \"Hello\". The command finds\nposition 5 (which is at the end of \"Hello\"), inserts \" World\", and returns\n\"Hello World\". Again, this creates a new state in the history.\n\nThe undo mechanism works by simply moving backwards through this history\ntimeline. When you call `Undo()`, the editor decreases the current position by\none, which makes the previous state become the current one. The editor doesn't\nneed to figure out how to reverse the operation because it already has the text\nfrom before the operation was performed.\n\nRedo works the opposite way. If you've undone some operations, there are states\nin the history that come after your current position. Calling `Redo()` moves the\ncurrent position forward to the next state in the history.\n\n## Example: Building a Shopping List\n\nImagine we're using our text editor to build a shopping list.\n\nWe start by creating an editor with a history limit of 10 operations:\n\n```go\neditor := editor.NewTextEditor(10)\n```\n\nAt this point, the editor has an empty history array and a current position of\n-1, meaning no operations have been performed yet.\n\nFirst, we add the title of our list:\n\n```go\neditor.Insert(0, \"Shopping List:\\n\")\n```\n\nThe editor creates an InsertCommand with position 0 and text \"Shopping List:\\n\".\nSince there's no current text (empty string), the command's Execute method\nreturns \"Shopping List:\\n\". The editor creates a new state containing this text\nand the command, adds it to the history, and sets the current position to 0.\n\nNext, we add our first item:\n\n```go\neditor.Insert(15, \"- Milk\\n\")\n```\n\nNow the InsertCommand executes against the current text \"Shopping List:\\n\". It\nfinds position 15 (right at the end), inserts \"- Milk\\n\", and returns \"Shopping\nList:\\n- Milk\\n\". This becomes state 1 in our history, and the current position\nmoves to 1.\n\nWe continue adding items:\n\n```go\neditor.Insert(23, \"- Bread\\n\")  // Results in \"Shopping List:\\n- Milk\\n- Bread\\n\"\neditor.Insert(32, \"- Eggs\\n\")   // Results in \"Shopping List:\\n- Milk\\n- Bread\\n- Eggs\\n\"\n```\n\nAt this point, our history contains four states (positions 0 through 3), each\nrepresenting what the shopping list looked like after each insertion.\n\nNow suppose we realize we don't need eggs. We can delete them:\n\n```go\neditor.Delete(32, 39)  // Delete \"- Eggs\\n\"\n```\n\nThe editor creates a DeleteCommand with start position 32 and end position 39.\nThis command's Execute method removes characters from position 32 to 38\n(remember, the end position is exclusive), returning \"Shopping List:\\n- Milk\\n-\nBread\\n\". This becomes state 4 in our history.\n\nIf we change our mind about removing eggs, we can undo:\n\n```go\neditor.Undo()  // Goes back to state 3: \"Shopping List:\\n- Milk\\n- Bread\\n- Eggs\\n\"\n```\n\nThe editor simply decreases the current position from 4 to 3, making the\nprevious state current again. No computation is needed because we already saved\nwhat the text looked like at that point.\n\nIf we undo again:\n\n```go\neditor.Undo()  // Goes back to state 2: \"Shopping List:\\n- Milk\\n- Bread\\n\"\n```\n\nNow if we decide to add a different item instead of eggs:\n\n```go\neditor.Insert(32, \"- Cheese\\n\")\n```\n\nSomething important happens here. Because we're not at the end of our history\nwhen we perform this new operation, the editor removes all the states that came\nafter our current position. States 3 and 4 (which contained the versions with\neggs) get discarded, and the new state with cheese becomes the new end of our\nhistory.\n\nThis behavior ensures that history remains linear and prevents confusing\nsituations where you could redo to multiple different futures.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiambelugax%2Fgoditor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fiambelugax%2Fgoditor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiambelugax%2Fgoditor/lists"}