[
  {
    "path": ".all-contributorsrc",
    "content": "{\n  \"files\": [\n    \"README.md\"\n  ],\n  \"imageSize\": 100,\n  \"commit\": false,\n  \"contributors\": [\n    {\n      \"login\": \"austincondiff\",\n      \"name\": \"Austin Condiff\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/806104?v=4\",\n      \"profile\": \"http://www.austincondiff.com\",\n      \"contributions\": [\n        \"design\",\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"lukepistrol\",\n      \"name\": \"Lukas Pistrol\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/9460130?v=4\",\n      \"profile\": \"http://lukaspistrol.com\",\n      \"contributions\": [\n        \"infra\",\n        \"test\",\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"thecoolwinter\",\n      \"name\": \"Khan Winter\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/35942988?v=4\",\n      \"profile\": \"https://blog.windchillmedia.com\",\n      \"contributions\": [\n        \"code\",\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"matthijseikelenboom\",\n      \"name\": \"Matthijs Eikelenboom\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1364843?v=4\",\n      \"profile\": \"https://github.com/matthijseikelenboom\",\n      \"contributions\": [\n        \"code\",\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"Wouter01\",\n      \"name\": \"Wouter Hennen\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/62355975?v=4\",\n      \"profile\": \"https://github.com/Wouter01\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"0xWDG\",\n      \"name\": \"Wesley De Groot\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1290461?v=4\",\n      \"profile\": \"https://wdg.codes\",\n      \"contributions\": [\n        \"infra\",\n        \"test\",\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"KaiTheRedNinja\",\n      \"name\": \"KaiTheRedNinja\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/88234730?v=4\",\n      \"profile\": \"https://github.com/KaiTheRedNinja\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"pkasila\",\n      \"name\": \"Pavel Kasila\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/17158860?v=4\",\n      \"profile\": \"https://github.com/pkasila\",\n      \"contributions\": [\n        \"infra\",\n        \"test\",\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"MarcoCarnevali\",\n      \"name\": \"Marco Carnevali\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/9656572?v=4\",\n      \"profile\": \"https://github.com/MarcoCarnevali\",\n      \"contributions\": [\n        \"infra\",\n        \"test\",\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"nanashili\",\n      \"name\": \"Nanashi Li\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/63672227?v=4\",\n      \"profile\": \"https://github.com/nanashili\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"RayZhao1998\",\n      \"name\": \"ninjiacoder\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/22616933?v=4\",\n      \"profile\": \"https://ninjiacoder.me\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Jeehut\",\n      \"name\": \"Cihat Gündüz\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/6942160?v=4\",\n      \"profile\": \"https://twitch.tv/Jeehut\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"MysteryCoder456\",\n      \"name\": \"Rehatbir Singh\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/43755491?v=4\",\n      \"profile\": \"https://github.com/MysteryCoder456\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Angelk90\",\n      \"name\": \"Angelk90\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/20476002?v=4\",\n      \"profile\": \"https://github.com/Angelk90\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"StefKors\",\n      \"name\": \"Stef Kors\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/11800807?v=4\",\n      \"profile\": \"https://www.stefkors.com\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"akring\",\n      \"name\": \"Chris Akring\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/6525286?v=4\",\n      \"profile\": \"https://akringblog.com/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"highjeans\",\n      \"name\": \"highjeans\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/77588045?v=4\",\n      \"profile\": \"https://github.com/highjeans\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"jasonplatts\",\n      \"name\": \"Jason Platts\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/48892071?v=4\",\n      \"profile\": \"https://github.com/jasonplatts\",\n      \"contributions\": [\n        \"infra\",\n        \"plugin\"\n      ]\n    },\n    {\n      \"login\": \"dzign1\",\n      \"name\": \"Rob Hughes\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/44317715?v=4\",\n      \"profile\": \"https://github.com/dzign1\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"lilingxi01\",\n      \"name\": \"Lingxi Li\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/36816148?v=4\",\n      \"profile\": \"https://lingxi.li\",\n      \"contributions\": [\n        \"code\",\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"octree\",\n      \"name\": \"HZ.Liu\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/7934444?v=4\",\n      \"profile\": \"https://github.com/octree\",\n      \"contributions\": [\n        \"code\",\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"richardtop\",\n      \"name\": \"Richard Topchii\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/8013017?v=4\",\n      \"profile\": \"https://www.youtube.com/channel/UCx1gvWpy5zjOd7yZyDwmXEA?sub_confirmation=1\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Pythonen\",\n      \"name\": \"Pythonen\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/53183345?v=4\",\n      \"profile\": \"https://github.com/Pythonen\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"jav-solo\",\n      \"name\": \"Javier Solorzano\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/10246220?v=4\",\n      \"profile\": \"https://github.com/jav-solo\",\n      \"contributions\": [\n        \"code\",\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"AngCosmin\",\n      \"name\": \"Cosmin Anghel\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/8146514?v=4\",\n      \"profile\": \"http://angcosmin.com\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"mmshivesh\",\n      \"name\": \"Shivesh\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/23611514?v=4\",\n      \"profile\": \"http://mmshivesh.ml\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"drucelweisse\",\n      \"name\": \"Andrey Plotnikov\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/36012972?v=4\",\n      \"profile\": \"https://github.com/drucelweisse\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"POPOBE97\",\n      \"name\": \"POPOBE97\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/7891810?v=4\",\n      \"profile\": \"https://github.com/POPOBE97\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"nrudnyk\",\n      \"name\": \"nrudnyk\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/20221382?v=4\",\n      \"profile\": \"https://github.com/nrudnyk\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"benkoska\",\n      \"name\": \"Ben Koska\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/17319613?v=4\",\n      \"profile\": \"https://github.com/benkoska\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"evolify\",\n      \"name\": \"evolify\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/12669069?v=4\",\n      \"profile\": \"https://github.com/evolify\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"shibotong\",\n      \"name\": \"Shibo Tong\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/44807628?v=4\",\n      \"profile\": \"https://github.com/shibotong\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"GetToSet\",\n      \"name\": \"Ethan Wong\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/8158163?v=4\",\n      \"profile\": \"https://ethanwong.me\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"gantoreno\",\n      \"name\": \"Gabriel Moreno\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/43397475?v=4\",\n      \"profile\": \"http://gantoreno.com\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"Prince213\",\n      \"name\": \"Sizhe Zhao\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/25235514?v=4\",\n      \"profile\": \"https://github.com/Prince213\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"Muhammed9991\",\n      \"name\": \"Muhammed Mahmood\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/80204376?v=4\",\n      \"profile\": \"https://github.com/Muhammed9991\",\n      \"contributions\": [\n        \"code\",\n        \"maintenance\"\n      ]\n    },\n    {\n      \"login\": \"muescha\",\n      \"name\": \"Muescha\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/184316?v=4\",\n      \"profile\": \"https://github.com/muescha\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"avdept\",\n      \"name\": \"Alex Sinelnikov\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1757017?v=4\",\n      \"profile\": \"https://alexsinelnikov.io/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Pribess\",\n      \"name\": \"Heewon Cho\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/72389357?v=4\",\n      \"profile\": \"http://pribess.github.io\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"MattKiazyk\",\n      \"name\": \"Matt Kiazyk\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1119565?v=4\",\n      \"profile\": \"https://www.xcodes.app\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"DingoBits\",\n      \"name\": \"DingoBits\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/107956274?v=4\",\n      \"profile\": \"https://github.com/DingoBits\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"sk409\",\n      \"name\": \"Shoto Kobayashi\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/25968819?v=4\",\n      \"profile\": \"https://github.com/sk409\",\n      \"contributions\": [\n        \"bug\",\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"aaryankotharii\",\n      \"name\": \"Aaryan Kothari\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/53724307?v=4\",\n      \"profile\": \"http://www.linkedin.com/in/aaryankotharii\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"Kyle-Ye\",\n      \"name\": \"Kyle\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/43724855?v=4\",\n      \"profile\": \"http://kyleye.top/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"NakaokaRei\",\n      \"name\": \"Nakaoka Rei\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/39183069?v=4\",\n      \"profile\": \"https://github.com/NakaokaRei\",\n      \"contributions\": [\n        \"code\",\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"alexdeem\",\n      \"name\": \"Alex Deem\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/404584?v=4\",\n      \"profile\": \"https://github.com/alexdeem\",\n      \"contributions\": [\n        \"maintenance\"\n      ]\n    },\n    {\n      \"login\": \"denizak\",\n      \"name\": \"deni zakya\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1758456?v=4\",\n      \"profile\": \"https://github.com/denizak\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"ahmdyasser\",\n      \"name\": \"Ahmad Yasser\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/42544598?v=4\",\n      \"profile\": \"https://github.com/ahmdyasser\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"ezraberch\",\n      \"name\": \"ezraberch\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/49635435?v=4\",\n      \"profile\": \"https://github.com/ezraberch\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Eliulm\",\n      \"name\": \"Elias Wahl\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/82230675?v=4\",\n      \"profile\": \"https://github.com/Eliulm\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"bombardier200\",\n      \"name\": \"bombardier200\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/25121427?v=4\",\n      \"profile\": \"https://github.com/bombardier200\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"yapryntsev\",\n      \"name\": \"Alex Yapryntsev\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/18378212?v=4\",\n      \"profile\": \"https://github.com/yapryntsev\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Code-DJ\",\n      \"name\": \"Code-DJ\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/8212554?v=4\",\n      \"profile\": \"https://github.com/Code-DJ\",\n      \"contributions\": [\n        \"code\",\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"neilZon\",\n      \"name\": \"Neilzon Viloria\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/46465568?v=4\",\n      \"profile\": \"https://github.com/neilZon\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"Cubik65536\",\n      \"name\": \"Cubik\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/72877496?v=4\",\n      \"profile\": \"https://cubik65536.top\",\n      \"contributions\": [\n        \"bug\",\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"RenanGreca\",\n      \"name\": \"Renan Greca\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/5760386?v=4\",\n      \"profile\": \"https://twitter.com/RenanGreca\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"maxkel\",\n      \"name\": \"maxkel\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/46418077?v=4\",\n      \"profile\": \"https://github.com/maxkel\",\n      \"contributions\": [\n        \"bug\",\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"scrapp08\",\n      \"name\": \"Scrap\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/105889363?v=4\",\n      \"profile\": \"https://scrapp08.xyz\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"iggy890\",\n      \"name\": \"iggy890\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/98705626?v=4\",\n      \"profile\": \"https://github.com/iggy890\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"stavares843\",\n      \"name\": \"Sara Tavares\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/29093946?v=4\",\n      \"profile\": \"https://github.com/stavares843\",\n      \"contributions\": [\n        \"bug\",\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"luah5\",\n      \"name\": \"luah5\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/128280019?v=4\",\n      \"profile\": \"https://github.com/luah5\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"evanlwang\",\n      \"name\": \"Evan Wang\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/71157264?v=4\",\n      \"profile\": \"https://github.com/evanlwang\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"dscyrescotti\",\n      \"name\": \"Dscyre Scotti\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/67727096?v=4\",\n      \"profile\": \"https://github.com/dscyrescotti\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"dvandyy\",\n      \"name\": \"Tomáš Boďa\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/40064599?v=4\",\n      \"profile\": \"http://tomasboda.com\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"Ahattalla\",\n      \"name\": \"Ahmed Attalla\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/53402452?v=4\",\n      \"profile\": \"https://github.com/Ahattalla\",\n      \"contributions\": [\n        \"bug\",\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"EstebanBorai\",\n      \"name\": \"Esteban Borai\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/34756077?v=4\",\n      \"profile\": \"http://estebanborai.com\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"avinizhanov\",\n      \"name\": \"avinizhanov\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/42622715?v=4\",\n      \"profile\": \"https://github.com/avinizhanov\",\n      \"contributions\": [\n        \"bug\",\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"kmohsin11\",\n      \"name\": \"kmohsin11\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/28269317?v=4\",\n      \"profile\": \"https://github.com/kmohsin11\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"armartinez\",\n      \"name\": \"Axel Martinez\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1909987?v=4\",\n      \"profile\": \"https://github.com/armartinez\",\n      \"contributions\": [\n        \"bug\",\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"FezVrasta\",\n      \"name\": \"Federico Zivolo\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/5382443?v=4\",\n      \"profile\": \"https://nivora.app\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"ElvisWong213\",\n      \"name\": \"Elvis Wong\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/40566101?v=4\",\n      \"profile\": \"https://github.com/ElvisWong213\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"ibrahimcetin\",\n      \"name\": \"İbrahim Çetin\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/33904390?v=4\",\n      \"profile\": \"http://ibrahimcetin.dev\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"phlpsong\",\n      \"name\": \"phlpsong\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/103433299?v=4\",\n      \"profile\": \"https://github.com/phlpsong\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"infinitepower18\",\n      \"name\": \"Ahnaf Mahmud\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/44692189?v=4\",\n      \"profile\": \"http://ahnafmahmud.com\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"DanKlaver15\",\n      \"name\": \"Dan K\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/9391497?v=4\",\n      \"profile\": \"https://github.com/DanKlaver15\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"knotbin\",\n      \"name\": \"Roscoe Rubin-Rottenberg\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/118622417?v=4\",\n      \"profile\": \"http://knotbin.xyz\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"plbstl\",\n      \"name\": \"Paul Ebose\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/49006567?v=4\",\n      \"profile\": \"https://github.com/plbstl\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"danielzsh\",\n      \"name\": \"Daniel Zhu\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/65467530?v=4\",\n      \"profile\": \"http://danielz.xyz\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"simonwhitaker\",\n      \"name\": \"Simon Whitaker\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/116432?v=4\",\n      \"profile\": \"https://github.com/simonwhitaker\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"LeonardoLarranaga\",\n      \"name\": \"Leonardo\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/83844690?v=4\",\n      \"profile\": \"https://github.com/LeonardoLarranaga\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"scaredcr6w\",\n      \"name\": \"Levente Anda\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/85457088?v=4\",\n      \"profile\": \"https://github.com/scaredcr6w\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"NobelLiu\",\n      \"name\": \"Nobel\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/10796646?v=4\",\n      \"profile\": \"https://nobelliu.github.com\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"SavelyUkuren\",\n      \"name\": \"Savely\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/125015568?v=4\",\n      \"profile\": \"https://github.com/SavelyUkuren\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Kihron\",\n      \"name\": \"Kihron\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/30128800?v=4\",\n      \"profile\": \"https://github.com/Kihron\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"pro100filipp\",\n      \"name\": \"Filipp Kuznetsov\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/12880697?v=4\",\n      \"profile\": \"https://github.com/pro100filipp\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"rustemd02\",\n      \"name\": \"rustemd02\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/11714456?v=4\",\n      \"profile\": \"https://github.com/rustemd02\",\n      \"contributions\": [\n        \"bug\",\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"SimonKudsk\",\n      \"name\": \"Simon Kudsk\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/10168417?v=4\",\n      \"profile\": \"https://github.com/SimonKudsk\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Syrux64\",\n      \"name\": \"Surya\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/118998822?v=4\",\n      \"profile\": \"https://github.com/Syrux64\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"georgetchelidze\",\n      \"name\": \"George Tchelidze\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/96194129?v=4\",\n      \"profile\": \"https://github.com/georgetchelidze\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"zhrispineda\",\n      \"name\": \"Chris Pineda\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/148818634?v=4\",\n      \"profile\": \"http://zhr.is\",\n      \"contributions\": [\n        \"code\"\n      ]\n    }\n  ],\n  \"contributorsPerLine\": 7,\n  \"projectName\": \"CodeEdit\",\n  \"projectOwner\": \"CodeEditApp\",\n  \"repoType\": \"github\",\n  \"repoHost\": \"https://github.com\",\n  \"skipCi\": true,\n  \"commitConvention\": \"angular\",\n  \"commitType\": \"docs\"\n}\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [CodeEditApp, austincondiff]\npatreon: # Replace with a single Patreon username\nopen_collective: codeeditapp\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\nlfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: 🐞 Bug report\ndescription: Something is not working as expected.\ntitle: 🐞 <bug title>\nlabels: bug\n\nbody:\n  - type: textarea\n    attributes:\n      label: Description\n      placeholder: >-\n        A clear and concise description of what the bug is...\n    validations:\n      required: true\n      \n  - type: textarea\n    attributes:\n      label: To Reproduce\n      description: >-\n        Steps to reliably reproduce the behavior.\n      placeholder: |\n        1. Go to '...'\n        2. Click on '....'\n        3. Scroll down to '....'\n        4. See error\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Expected Behavior\n      placeholder: >-\n        A clear and concise description of what you expected to happen...\n    validations:\n      required: true\n      \n  - type: textarea\n    attributes:\n      label: Version Information\n      description: >-\n         click on the version number on the welcome screen\n      value: |\n        CodeEdit: [e.g. 0.0.x-alpha.y]\n        macOS: [e.g. 13.2.1]\n        Xcode: [e.g. 14.2]\n\n  - type: textarea\n    attributes:\n      label: Additional Context\n      placeholder: >-\n        Any other context or considerations about the bug...\n      \n  - type: textarea\n    attributes:\n      label: Screenshots\n      placeholder: >-\n        If applicable, please provide relevant screenshots or screen recordings...\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: ✨ Feature request\ndescription: Suggest an idea for this project\ntitle: ✨ <feature title>\nlabels: enhancement\n\nbody:\n  - type: textarea\n    attributes:\n      label: Description\n      placeholder: >-\n        A clear and concise description of what you would like to happen...\n    validations:\n      required: true\n      \n  - type: textarea\n    attributes:\n      label: Alternatives Considered\n      placeholder: >-\n        Any alternative solutions or features you've considered...\n\n  - type: textarea\n    attributes:\n      label: Additional Context\n      placeholder: >-\n        Any other context or considerations about the feature request...\n      \n  - type: textarea\n    attributes:\n      label: Screenshots\n      placeholder: >-\n        If applicable, please provide relevant screenshots or screen recordings...\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "<!--- IMPORTANT: If this PR addresses multiple unrelated issues, it will be closed until separated. -->\n\n### Description\n\n<!--- REQUIRED: Describe what changed in detail -->\n\n### Related Issues\n\n<!--- REQUIRED: Tag all related issues (e.g. * #123) -->\n<!--- If this PR resolves the issue please specify (e.g. * closes #123) -->\n<!--- If this PR addresses multiple issues, these issues must be related to one other -->\n\n* #ISSUE_NUMBER\n\n### Checklist\n\n<!--- Add things that are not yet implemented above -->\n\n- [ ] I read and understood the [contributing guide](https://github.com/CodeEditApp/CodeEdit/blob/main/CONTRIBUTING.md) as well as the [code of conduct](https://github.com/CodeEditApp/CodeEdit/blob/main/CODE_OF_CONDUCT.md)\n- [ ] The issues this PR addresses are related to each other\n- [ ] My changes generate no new warnings\n- [ ] My code builds and runs on my machine\n- [ ] My changes are all related to the related issue above\n- [ ] I documented my code\n\n### Screenshots\n\n<!--- REQUIRED: if issue is UI related -->\n\n<!--- IMPORTANT: Fill out all required fields. Otherwise we might close this PR temporarily -->\n"
  },
  {
    "path": ".github/release-drafter.yml",
    "content": "categories:\n  - title: '✨ Enhancements'\n    labels:\n      - 'enhancement'\n      - 'feature'\n  - title: '🐞 Bug Fixes'\n    labels:\n      - 'bugfix'\n      - 'bug'\n  - title: '🧰 Maintenance'\n    label:\n      - 'chore'\n      - 'documentation'\n      - 'dependencies'\ntemplate: |\n  <!-- Update Template --> \n\n  ## Changes\n\n  $CHANGES\n  \n  <!-- sparkle:edSignature=xxxxx -->\n"
  },
  {
    "path": ".github/scripts/remove_todos.sh",
    "content": "#!/bin/bash\ngrep -rl TODO . | xargs sed -i 's/TODO/notaTODO/g'\ngrep -rl FIXME . | xargs sed -i 's/FIXME/notaFIXME/g'\n"
  },
  {
    "path": ".github/scripts/test_app.sh",
    "content": "#!/bin/bash\n\nARCH=\"\"\n\nif [ $# -eq 0 ]; then\n    ARCH=\"x86_64\"\nelif [ $1 = \"arm\" ]; then\n    ARCH=\"arm64\"\nfi\n\necho \"Building with Xcode: $(xcodebuild -version)\"\necho \"Building with arch: ${ARCH}\"\necho \"SwiftLint Version: $(swiftlint --version)\"\n\nexport LC_CTYPE=en_US.UTF-8\n\n# xcbeautify flags: \n# - renderer: render to gh actions\n# - q: quiet output\n# - is-ci: include test results in output\n\nset -o pipefail && arch -\"${ARCH}\" xcodebuild \\\n           -scheme CodeEdit \\\n           -destination \"platform=OS X,arch=${ARCH}\" \\\n           -skipPackagePluginValidation \\\n           clean test | xcbeautify --renderer github-actions -q --is-ci\n"
  },
  {
    "path": ".github/scripts/test_version_number.sh",
    "content": "#!/bin/bash\n\n# get the version number from the Info.plist file using agvtool\nVERS=$(xcrun agvtool mvers -terse1)\n\n# if the version number is empty, exit with an error\nif [ \"$VERS\" == \"\" ]; then\n  echo \"No version number found\"\n  echo \"Make sure Info.plist has a CFBundleShortVersionString key\"\n  echo \"\"\n  echo \"If Info.plist does not have a CFBundleShortVersionString key, add the following to your Info.plist file:\"\n  echo \"\"\n  echo \"<key>CFBundleShortVersionString</key>\"\n  echo \"<string>0.0.1</string>\"\n  echo \"\"\n  echo \"For more information see https://github.com/CodeEditApp/CodeEdit/pull/1006\"\n  exit 1 # exit with an error\nelse\n  echo \"Version number is $VERS\"\n  exit 0 # exit with no error\nfi\n"
  },
  {
    "path": ".github/stale.yml",
    "content": "# Number of days of inactivity before an issue becomes stale\ndaysUntilStale: 60\n# Number of days of inactivity before a stale issue is closed\ndaysUntilClose: 7\n# Issues with these labels will never be considered stale\nexemptLabels:\n  - pinned\n  - security\n# Label to use when marking an issue as stale\nstaleLabel: wontfix\n# Comment to post when marking an issue as stale. Set to `false` to disable\nmarkComment: >\n  This issue has been automatically marked as stale because it has not had\n  recent activity. It will be closed if no further activity occurs. Thank you\n  for your contributions.\n# Comment to post when closing a stale issue. Set to `false` to disable\ncloseComment: false\n"
  },
  {
    "path": ".github/workflows/CI-bump-build-number.yml",
    "content": "name: CI - Update Build Number\non:\n  workflow_dispatch:\njobs:\n  upadate-build-number:\n    name: Update Build Number\n    runs-on: [self-hosted, macOS]\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v3\n  \n      - name: Bump Build Number\n        run: |\n          xcrun agvtool next-version -all\n          APP_BUILD=$(xcrun agvtool vers -terse)\n          echo \"APP_BUILD=$APP_BUILD\" >> $GITHUB_ENV\n\n      - name: Commit Changes\n        env:\n          APP_BUILD: ${{ env.APP_BUILD }}\n        run: |\n          git add .\n          git config --local user.email \"action@github.com\"\n          git config --local user.name \"GitHub Action\"\n          git commit -m \"bump build number to $APP_BUILD\"\n      - name: Push Changes\n        uses: ad-m/github-push-action@v0.6.0\n        with:\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n          branch: temp/bump-build-number\n          force: true\n      - name: Create Pull Request\n        uses: peter-evans/create-pull-request@v4\n        env:\n          APP_BUILD: ${{ env.APP_BUILD }}\n        with:\n          branch: temp/bump-build-number\n          delete-branch: true\n          base: main\n          title: Bump Build Number to ${{ env.APP_BUILD }}\n          body: Automatically bump build number of all targets to ${{ env.APP_BUILD }}\n"
  },
  {
    "path": ".github/workflows/CI-pre-release.yml",
    "content": "name: CI - Pre Release\non:\n  workflow_dispatch:\njobs:\n  swiftlint:\n    name: SwiftLint\n    uses: ./.github/workflows/lint.yml\n    secrets: inherit\n  test:\n    name: Testing CodeEdit\n    needs: swiftlint\n    uses: ./.github/workflows/tests.yml\n    secrets: inherit\n  deploy:\n    if: github.repository_owner == 'CodeEditApp'\n    name: Deploying CodeEdit [Prerelease]\n    needs: [swiftlint, test]\n    uses: ./.github/workflows/pre-release.yml\n    secrets: inherit\n  ReleaseDrafter:\n    name: Release Drafter\n    needs: [swiftlint, test, deploy]\n    uses: ./.github/workflows/release-drafter.yml\n"
  },
  {
    "path": ".github/workflows/CI-pull-request.yml",
    "content": "name: CI - Pull Request\non:\n  pull_request:\n    branches:\n      - 'main'\n  workflow_dispatch:\njobs:\n  swiftlint:\n    name: SwiftLint\n    uses: ./.github/workflows/lint.yml\n    secrets: inherit\n  test:\n    name: Testing CodeEdit\n    needs: swiftlint\n    uses: ./.github/workflows/tests.yml\n    secrets: inherit\n"
  },
  {
    "path": ".github/workflows/CI-release-notes.yml",
    "content": "name: Deploy Website on Release Note Changes\n\non:\n  workflow_dispatch:\n  release:\n    types: [created, edited, deleted]\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Send deploy request to Vercel\n        run: curl -X POST ${{ secrets.VERCEL_DEPLOY_URL }}\n"
  },
  {
    "path": ".github/workflows/add-to-project.yml",
    "content": "name: Add new issues to project\n\non:\n  issues:\n    types:\n      - opened\n\njobs:\n  add-to-project:\n    name: Add new issues labeled with enhancement or bug to project\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/add-to-project@v0.4.0\n        with:\n          # You can target a repository in a different organization\n          # to the issue\n          project-url: https://github.com/orgs/CodeEditApp/projects/3\n          github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}\n          labeled: enhancement, bug\n          label-operator: OR\n"
  },
  {
    "path": ".github/workflows/appcast.yml",
    "content": "name: Build and publish a new appcast file\n\non:\n  workflow_dispatch:\n\njobs:\n  jekyll:\n    runs-on: ubuntu-latest\n    steps:\n    - name: Checkout 🛎\n      uses: actions/checkout@v3\n      with:\n        # If you're using actions/checkout@v3 you must set persist-credentials to false in most cases for the deployment to work correctly.\n        persist-credentials: false\n\n    - name: Cache 📦\n      uses: actions/cache@v3.0.8\n      with:\n        path: AppCast/vendor/bundle\n        key: ${{ runner.os }}-gems-v1.0-${{ hashFiles('AppCast/Gemfile') }}\n        restore-keys: |\n          ${{ runner.os }}-gems-\n          \n    - name: Ruby ♦️\n      uses: actions/setup-ruby@v1.1.3\n      with:\n        ruby-version: '2.7'\n\n    - name: Bundler 💎\n      working-directory: AppCast\n      env:\n        BUNDLE_PATH: vendor/bundle\n      run: |\n        gem install bundler\n        bundle install\n\n    - name: Build 🛠\n      working-directory: AppCast\n      env:\n        BUNDLE_PATH: vendor/bundle\n        JEKYLL_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      run: bundle exec jekyll build\n      \n    - name: Publish 🚀\n      uses: JamesIves/github-pages-deploy-action@releases/v3\n      with:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        BRANCH: gh-pages\n        FOLDER: AppCast/_site\n"
  },
  {
    "path": ".github/workflows/issue.yml",
    "content": "name: Labeling new issue\non:\n  issues:\n    types: ['opened']\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: Renato66/auto-label@v2\n        with:\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n          ignore-comments: true\n          labels-synonyms: '{\"bug\":[\"error\",\"need fix\",\"not working\"],\"enhancement\":[\"upgrade\"],\"question\":[\"help\"]}'\n          labels-not-allowed: '[\"good first issue\", \"priority\"]'\n          default-labels: '[\"triage needed\"]'\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: SwiftLint\n\non:\n  workflow_dispatch:\n  workflow_call:\n\njobs:\n  SwiftLint:\n    runs-on: [self-hosted, macOS] # ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - name: GitHub Action for SwiftLint\n        run: swiftlint --reporter github-actions-logging --strict\n"
  },
  {
    "path": ".github/workflows/pre-release.yml",
    "content": "name: Build and Publish Pre-release\n\non:\n  workflow_dispatch:\n  workflow_call:\n\njobs:\n  pre-release:\n    name: Build and Publish Pre-release\n    runs-on: [self-hosted, macOS]\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v3\n\n      ############################\n      # Install Certificate\n      ############################\n      - name: Install codesign certificate\n        env:\n          # DEV_CERT_B64: Base64-encoded developer certificate as .p12\n          # DEV_CERT_PWD: Developer certificate .p12 password\n          # PROVISION_PROFILE_B64: Base64-encoded provisioning profile as .provisionprofile\n          # KEYCHAIN_TIMEOUT: Lock keychain after timeout interval\n          # https://docs.github.com/en/actions/deployment/deploying-xcode-applications/installing-an-apple-certificate-on-macos-runners-for-xcode-development\n          DEV_CERT_B64: ${{ secrets.DEV_CERT_B64 }}\n          DEV_CERT_PWD: ${{ secrets.DEV_CERT_PWD }}\n          MAC_CERT_B64: ${{ secrets.MAC_CERT_B64 }}\n          MAC_CERT_PWD: ${{ secrets.MAC_CERT_PWD }}\n          # PROVISION_PROFILE_B64: ${{ secrets.PROVISION_PROFILE_B64 }}\n          KEYCHAIN_TIMEOUT: 21600\n        run: |\n          DEV_CERT_P12=\"$RUNNER_TEMP/dev_cert.p12\"\n          MAC_CERT_P12=\"$RUNNER_TEMP/mac_cert.p12\"\n          KEYCHAIN_DB=\"$RUNNER_TEMP/keychain.keychain-db\"\n          KEYCHAIN_PWD=$(openssl rand -base64 24)\n          security create-keychain -p \"$KEYCHAIN_PWD\" \"$KEYCHAIN_DB\"\n          security set-keychain-settings -lut \"$KEYCHAIN_TIMEOUT\" \"$KEYCHAIN_DB\"\n          security unlock-keychain -p \"$KEYCHAIN_PWD\" \"$KEYCHAIN_DB\"\n          echo -n \"$DEV_CERT_B64\" | base64 --decode -o \"$DEV_CERT_P12\"\n          security import \"$DEV_CERT_P12\" -P \"$DEV_CERT_PWD\" -A -t cert -f pkcs12 -k \"$KEYCHAIN_DB\"\n          echo -n \"$MAC_CERT_B64\" | base64 --decode -o \"$MAC_CERT_P12\"\n          security import \"$MAC_CERT_P12\" -P \"$MAC_CERT_PWD\" -A -t cert -f pkcs12 -k \"$KEYCHAIN_DB\"\n          security list-keychain -d user -s \"$KEYCHAIN_DB\"\n      \n      ############################\n      # Build\n      ############################\n      - name: Build CodeEdit\n        env:\n          APPLE_TEAM_ID:  ${{ secrets.APPLE_TEAM_ID }}\n        run: xcodebuild -scheme CodeEdit -configuration Pre -derivedDataPath \"$RUNNER_TEMP/DerivedData\" -archivePath \"$RUNNER_TEMP/CodeEdit.xcarchive\" -skipPackagePluginValidation DEVELOPMENT_TEAM=$APPLE_TEAM_ID archive | xcpretty\n      \n      ############################\n      # Sign\n      ############################\n      - name: Sign CodeEdit\n        env:\n          CODESIGN_SIGN: ${{ secrets.CODESIGN_SIGN }}\n        run: |\n          REV=$(git rev-parse --short HEAD)\n          echo \"REV=$REV\" >> $GITHUB_ENV\n          codesign --sign \"$CODESIGN_SIGN\" -vvv --verbose --strict --options=runtime --prefix app.codeedit.CodeEdit. --force --timestamp \"$RUNNER_TEMP/CodeEdit.xcarchive/Products/Applications/CodeEdit.app/Contents/Frameworks/CodeEditKit.framework\"\n          codesign --sign \"$CODESIGN_SIGN\" -vvv --verbose --strict --options=runtime --prefix app.codeedit.CodeEdit. --force --timestamp \"$RUNNER_TEMP/CodeEdit.xcarchive/Products/Applications/CodeEdit.app/Contents/Frameworks/CodeLanguages_Container.framework\"\n          codesign --sign \"$CODESIGN_SIGN\" -vvv --verbose --strict --options=runtime --prefix app.codeedit.CodeEdit. --force --deep --timestamp \"$RUNNER_TEMP/CodeEdit.xcarchive/Products/Applications/CodeEdit.app/Contents/Frameworks/Sparkle.framework\"\n          codesign --sign \"$CODESIGN_SIGN\" -vvv --verbose --strict --options=runtime --prefix app.codeedit.CodeEdit. --force --timestamp --entitlements \"$RUNNER_TEMP/../CodeEdit/CodeEdit/OpenWithCodeEdit/OpenWithCodeEdit.entitlements\" \"$RUNNER_TEMP/CodeEdit.xcarchive/Products/Applications/CodeEdit.app/Contents/PlugIns/OpenWithCodeEdit.appex\"\n          codesign --sign \"$CODESIGN_SIGN\" -vvv --verbose --strict --options=runtime --prefix app.codeedit.CodeEdit. --force --timestamp \"$RUNNER_TEMP/CodeEdit.xcarchive/Products/Applications/CodeEdit.app\"\n\n      ############################\n      # DMG & Notarize\n      ############################\n      - name: Create DMG & Notarize\n        env:\n          CODESIGN_SIGN: ${{ secrets.CODESIGN_SIGN }}\n          APPLE_ID: ${{ secrets.APPLE_ID }}\n          APPLE_ID_PWD: ${{ secrets.APPLE_ID_PWD }}\n          APPLE_TEAM_ID:  ${{ secrets.APPLE_TEAM_ID }}\n          RUNNER_PW: ${{ secrets.RUNNER_PW }}\n        run: |\n          REV=$(git rev-parse --short HEAD)\n          echo \"REV=$REV\" >> $GITHUB_ENV\n          security unlock-keychain -p \"$RUNNER_PW\"\n          xcrun notarytool store-credentials CodeEdit --apple-id \"$APPLE_ID\" --team-id \"$APPLE_TEAM_ID\" --password \"$APPLE_ID_PWD\"\n          cp \"$RUNNER_TEMP/../CodeEdit/CodeEdit/Resources/dmgBackground.png\" \"$RUNNER_TEMP/.background.png\"\n          epochdate=$(($(date +'%s * 1000 + %-N / 1000000')))\n          tcc_service_appleevents=\"replace into access (service,client,client_type,auth_value,auth_reason,auth_version,indirect_object_identifier_type,indirect_object_identifier,flags,last_modified) values (\\\"kTCCServiceAppleEvents\\\",\\\"/usr/sbin/sshd\\\",1,2,4,1,0,\\\"com.apple.finder\\\",0,$epochdate);\"\n          echo $RUNNER_PW | sudo -S sqlite3 \"/Users/administrator/Library/Application Support/com.apple.TCC/TCC.db\" \"$tcc_service_appleevents\"\n          create-dmg \\\n            --volname \"CodeEdit\" \\\n            --window-pos 200 120 \\\n            --window-size 699 518 \\\n            --background \"$RUNNER_TEMP/.background.png\" \\\n            --icon-size 128 \\\n            --icon \"CodeEdit.app\" 170 210 \\\n            --hide-extension \"CodeEdit.app\" \\\n            --app-drop-link 530 210 \\\n            --codesign \"$CODESIGN_SIGN\" \\\n            --notarize \"CodeEdit\" \\\n            \"$RUNNER_TEMP/CodeEdit.dmg\" \\\n            \"$RUNNER_TEMP/CodeEdit.xcarchive/Products/Applications/\"\n          echo $RUNNER_PW | sudo -S security lock-keychain\n          \n      ############################\n      # Get Version and Build number\n      ############################\n      - name: Get Version and Build number\n        run: |\n          APP_VERSION=$(xcrun agvtool mvers -terse1)\n          APP_BUILD=$(xcrun agvtool vers -terse)\n          echo \"APP_VERSION=$APP_VERSION\" >> $GITHUB_ENV\n          echo \"APP_BUILD=$APP_BUILD\" >> $GITHUB_ENV\n\n      ############################\n      # Upload dSYMs Artifact\n      ############################\n      - name: Upload dSYMs Artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: \"CodeEdit-${{ env.APP_BUILD }}-dSYMs\"\n          path: \"${{ RUNNER.TEMP }}/CodeEdit.xcarchive/dSYMs\"\n          if-no-files-found: error\n          # overwrite files for the same build number\n          overwrite: true\n          # these can be big, use maximum compression\n          compression-level: 9\n\n      ############################\n      # Sparkle Appcast\n      ############################\n      - name: Update Sparkle appcast\n        env:\n          # SPARKLE_KEY: Sparkle EdDSA key exported from `generate_keys -x` as plain text\n          # SPARKLE_CHANNEL: Seperate dev builds from default channel, to be specified in [SPUUpdaterDelegate allowedChannelsForUpdater:]\n          # SPARKLE_DL_PREFIX: Prefix for the URL from where updates will be downloaded\n          # SPARKLE_LINK: CodeEdit Website\n          #   https://github.com/CodeEditApp/CodeEdit/releases/download/0.0.1-alpha.11/CodeEdit-9113dc5.dmg\n          # RELEASE_NOTES_PREFIX: The URL to prefix before an update link:\n          #   https://codeedit.app/whats-new/raw/{v0.1.0} -- data in {} is inserted by sparkle\n          # RELEASE_NOTES_URL: The URL of the entire release notes page: https://codeedit.app/whats-new\n          SPARKLE_KEY: ${{ secrets.SPARKLE_KEY }}\n          SPARKLE_CHANNEL: dev\n          SPARKLE_DL_PREFIX: \"https://github.com/CodeEditApp/CodeEdit/releases/download\"\n          SPARKLE_LINK: \"https://github.com/CodeEditApp/CodeEdit\"\n          APP_VERSION: ${{ env.APP_VERSION }}\n          APP_BUILD: ${{ env.APP_BUILD }}\n          RELEASE_NOTES_URL: \"https://codeedit.app/whats-new/\"\n          RELEASE_NOTES_PREFIX: \"https://codeedit.app/sparkle/\"\n        run: |\n          SPARKLE_BIN=\"$RUNNER_TEMP/DerivedData/SourcePackages/artifacts/sparkle/Sparkle/bin\"\n          SPARKLE_ARCHIVE=\"$RUNNER_TEMP/Sparkle_Archive\"\n          echo -n \"$SPARKLE_KEY\" | tee \"$RUNNER_TEMP/sparkle_key\"\n          mkdir \"$SPARKLE_ARCHIVE\"\n          cp \"$RUNNER_TEMP/CodeEdit.dmg\" \"$SPARKLE_ARCHIVE\"\n          SPARKLE_SIG=$(\"$SPARKLE_BIN/sign_update\" --ed-key-file \"$RUNNER_TEMP/sparkle_key\" \"$SPARKLE_ARCHIVE/CodeEdit.dmg\" | cut -d\\\" -f2)\n          echo \"<!DOCTYPE>\" > \"$SPARKLE_ARCHIVE/CodeEdit.html\" # Need a blank html doc with the DOCTYPE tag to trick sparkle into loading our remote release notes.\n          \"$SPARKLE_BIN/generate_appcast\" --ed-key-file \"$RUNNER_TEMP/sparkle_key\" --download-url-prefix \"${{ env.SPARKLE_DL_PREFIX }}/v${{ env.APP_VERSION }}/\" --link \"$SPARKLE_LINK\" --channel \"$SPARKLE_CHANNEL\" --maximum-deltas 0 \"$SPARKLE_ARCHIVE\" --release-notes-url-prefix \"${{ env.RELEASE_NOTES_PREFIX }}v${{ env.APP_VERSION }}/\" --full-release-notes-url \"$RELEASE_NOTES_URL\"\n\n      ############################\n      # Publish Pre Release\n      ############################\n      - name: Publish Pre-release\n        uses: marvinpinto/action-automatic-releases@latest\n        env:\n          APP_VERSION: ${{ env.APP_VERSION }}\n          APP_BUILD: ${{ env.APP_BUILD }}\n        with:\n          title: \"v${{ env.APP_VERSION }}\"\n          files: |\n            ${{ RUNNER.TEMP }}/Sparkle_Archive/CodeEdit.dmg\n            ${{ RUNNER.TEMP }}/Sparkle_Archive/appcast.xml\n          automatic_release_tag: \"v${{ env.APP_VERSION }}\"\n          prerelease: false\n          repo_token: \"${{ secrets.GITHUB_TOKEN }}\"\n          draft: true\n\n      ############################\n      # Cleanup Secrets\n      ############################\n      - name: Clean up keychain and provisioning profile\n        if: ${{ always() }}\n        run: |\n          security delete-keychain \"$RUNNER_TEMP/keychain.keychain-db\"\n          rm -rf \"~/Library/MobileDevice/Provisioning Profiles\"\n"
  },
  {
    "path": ".github/workflows/release-drafter.yml",
    "content": "name: Release Drafter\n\non:\n  # Allow running it manually in case we forget to label a PR before merging\n  workflow_dispatch:\n  workflow_call:\n\njobs:\n  update_release_draft:\n    runs-on: ubuntu-latest\n    steps:\n      - id: release\n        uses: release-drafter/release-drafter@v5\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: Tests\non:\n  workflow_dispatch:\n  workflow_call:\njobs:\n  test:\n    name: Testing CodeEdit\n    runs-on: [self-hosted, macOS]\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v3\n      - name: Check Version Number exists\n        run: exec ./.github/scripts/test_version_number.sh\n      - name: Testing App\n        run: exec ./.github/scripts/test_app.sh arm\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n# Xcode\n#\n# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore\n\n## User settings\nxcuserdata/\n\n## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)\n*.xcscmblueprint\n*.xccheckout\n\n## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)\nbuild/\nDerivedData/\n*.moved-aside\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspectivev3\n!default.perspectivev3\n\n## Obj-C/Swift specific\n*.hmap\n\n## App packaging\n*.ipa\n*.dSYM.zip\n*.dSYM\n\n## Playgrounds\ntimeline.xctimeline\nplayground.xcworkspace\n\n# Swift Package Manager\n#\n# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.\n# Packages/\n# Package.pins\n# Package.resolved\n# *.xcodeproj\n#\n# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata\n# hence it is not needed unless you have added a package configuration file to your project\n# .swiftpm\n\n.build/\n\n# CocoaPods\n#\n# We recommend against adding the Pods directory to your .gitignore. However\n# you should judge for yourself, the pros and cons are mentioned at:\n# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control\n#\n# Pods/\n#\n# Add this line if you want to avoid checking in source code from the Xcode workspace\n# *.xcworkspace\n\n# Carthage\n#\n# Add this line if you want to avoid checking in source code from Carthage dependencies.\n# Carthage/Checkouts\n\nCarthage/Build/\n\n# Accio dependency management\nDependencies/\n.accio/\n\n# fastlane\n#\n# It is recommended to not store the screenshots in the git repo.\n# Instead, use fastlane to re-generate the screenshots whenever they are needed.\n# For more information about the recommended setup visit:\n# https://docs.fastlane.tools/best-practices/source-control/#source-control\n\nfastlane/report.xml\nfastlane/Preview.html\nfastlane/screenshots/**/*.png\nfastlane/test_output\n\n# Code Injection\n#\n# After new code Injection tools there's a generated folder /iOSInjectionProject\n# https://github.com/johnno1962/injectionforxcode\n\niOSInjectionProject/\n\n# System files\n\n.DS_Store\n.codeedit\n.idea\n.vscode\n"
  },
  {
    "path": ".swiftlint.yml",
    "content": "disabled_rules:\n  - todo\n  - trailing_comma\n  - nesting\n\ntype_name:\n  excluded:\n    - ID\n\nidentifier_name:\n  allowed_symbols: [\"$\", \"_\"]\n  excluded:\n    - id\n    - vc\n\n# paths to ignore during linting.\nexcluded:\n  - CodeEditModules/.build # Where Swift Package Manager checks out dependency sources\n  - DerivedData\n\nopt_in_rules:\n  - attributes\n  - empty_count\n  - closure_spacing\n  - contains_over_first_not_nil\n  - missing_docs\n#  - implicit_return\n  - modifier_order\n  - convenience_type\n  - pattern_matching_keywords\n  - multiline_parameters_brackets\n  - multiline_arguments_brackets\n\ncustom_rules:\n  spaces_over_tabs:\n    included: \".*\\\\.swift\"\n    name: \"Spaces over Tabs\"\n    regex: \"\\t\"\n    message: \"Prefer spaces for indents over tabs. See Xcode setting: 'Text Editing' -> 'Indentation'\"\n    severity: warning\n"
  },
  {
    "path": "AppCast/.gitignore",
    "content": "_site\n.sass-cache\n.jekyll-metadata\n"
  },
  {
    "path": "AppCast/Gemfile",
    "content": "source \"https://rubygems.org\"\n\n# Hello! This is where you manage which Jekyll version is used to run.\n# When you want to use a different version, change it below, save the\n# file and run `bundle install`. Run Jekyll with `bundle exec`, like so:\n#\n#     bundle exec jekyll serve\n#\n# This will help ensure the proper Jekyll version is running.\n# Happy Jekylling!\ngem \"jekyll\", \"~> 3.9.0\"\n\ngem \"jekyll-github-metadata\", group: :jekyll_plugins\n\n# Windows does not include zoneinfo files, so bundle the tzinfo-data gem\n# and associated library.\ninstall_if -> { RUBY_PLATFORM =~ %r!mingw|mswin|java! } do\n  gem \"tzinfo\", \"~> 1.2\"\n  gem \"tzinfo-data\"\nend\n\n# Performance-booster for watching directories on Windows\ngem \"wdm\", \"~> 0.1.0\", :install_if => Gem.win_platform?\n\n# kramdown v2 ships without the gfm parser by default. If you're using\n# kramdown v1, comment out this line.\ngem \"kramdown-parser-gfm\"\n\n"
  },
  {
    "path": "AppCast/_config.yml",
    "content": "# Welcome to Jekyll!\n#\n# This config file is meant for settings that affect your whole blog, values\n# which you are expected to set up once and rarely edit after that. If you find\n# yourself editing this file very often, consider using Jekyll's data files\n# feature for the data you need to update frequently.\n#\n# For technical reasons, this file is *NOT* reloaded automatically when you use\n# 'bundle exec jekyll serve'. If you change this file, please restart the server process.\n\n# Site settings\n# These are used to personalize your new site. If you look in the HTML files,\n# you will see them accessed via {{ site.title }}, {{ site.email }}, and so on.\n# You can create any custom variable you would like, and they will be accessible\n# in the templates via {{ site.myvariable }}.\ntitle: CodeEdit.app\ndescription: >- # this means to ignore newlines until \"baseurl:\"\nbaseurl: \"\" # the subpath of your site, e.g. /blog\nurl: \"\" # the base hostname & protocol for your site, e.g. http://example.com\n\n# Build settings\nmarkdown: kramdown\nplugins:\n  - \"jekyll-github-metadata\"\n\n# Exclude from processing.\n# The following items will not be processed, by default. Create a custom list\n# to override the default setting.\n# exclude:\n#   - Gemfile\n#   - Gemfile.lock\n#   - node_modules\n#   - vendor/bundle/\n#   - vendor/cache/\n#   - vendor/gems/\n#   - vendor/ruby/\n"
  },
  {
    "path": "AppCast/_includes/appcast.inc",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<rss version=\"2.0\" xmlns:sparkle=\"http://www.andymatuschak.org/xml-namespaces/sparkle\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n    <channel>\n        <title>{{ site.github.project_title }}</title>\n        <description>Most recent changes with links to updates.</description>\n        <language>en</language>\n        {% for release in site.github.releases %}\n            {% unless release.draft %}\n                {% unless release.prerelease and page.release_only %}\n                    <item>\n                        <title>{{ release.name }}</title>\n                        <description><![CDATA[{{ release.body | markdownify }}]]></description>\n                        <pubDate>{{ release.published_at | date_to_rfc822 }}</pubDate>\n                        {% for asset in release.assets limit:1 %}\n                            {% assign signature = release.body | sparkle_signature %}\n\n                            {% assign build_nums = release.tag_name | replace_first:'v','' | replace_first:'b',',' | split:',' %}\n                            {% if build_nums.size == 2 %}\n                                {% assign version_number = build_nums[0] %}\n                                {% assign build_number = build_nums[1] %}\n\n                                <enclosure\n                                    url=\"{{ asset.browser_download_url }}\"\n                                    sparkle:version=\"{{ build_number }}\"\n                                    sparkle:shortVersionString=\"{{ version_number }}\"\n                                    sparkle:edSignature=\"{{ signature }}\"\n                                    length=\"{{ asset.size }}\"\n                                    type=\"application/octet-stream\" />\n                            {% else %}\n                                {% assign version = release.tag_name | remove_first:'v' %}\n\n                                <enclosure\n                                    url=\"{{ asset.browser_download_url }}\"\n                                    sparkle:version=\"{{ version }}\"\n                                    sparkle:edSignature=\"{{ signature }}\"\n                                    length=\"{{ asset.size }}\"\n                                    type=\"application/octet-stream\" />\n                            {% endif %}\n                        {% endfor %}\n                    </item>\n                {% endunless %}\n            {% endunless %}\n        {% endfor %}\n    </channel>\n</rss>\n"
  },
  {
    "path": "AppCast/_plugins/signature_filter.rb",
    "content": "module Jekyll\n  module SignatureFilter\n    def sparkle_signature(release_body)\n      regex = /<!-- sparkle:edSignature=(?<signature>.*) -->/m\n      signature = release_body.match(regex).named_captures[\"signature\"]\n      raise \"Didn't find a signature in the release body.\" if signature.empty?\n      signature\n    end\n  end\nend\n\nLiquid::Template.register_filter(Jekyll::SignatureFilter)"
  },
  {
    "path": "AppCast/appcast.xml",
    "content": "---\nrelease_only: true\n---\n{%include appcast.inc %}\n"
  },
  {
    "path": "AppCast/appcast_pre.xml",
    "content": "---\nrelease_only: false\n---\n{%include appcast.inc %}\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Repeated low-quality pull requests or pull requests not in accordance to project goals especially after warning from a project maintainer or administrator\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\n[Github Issues](https://github.com/CodeEditApp/CodeEdit/issues/new).\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior,  harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contribute to CodeEdit\n\nFeel free to join and collaborate on our [Discord Server](https://discord.gg/vChUXVf9Em).\n\n> [!WARNING]\n> Please do not submit `localization` related pull requests at this time. \n> Once we are ready to support more languages we will let you know with a guide on how to contribute here and on our [Discord Server](https://discord.gg/vChUXVf9Em).\n\n## Fork & Clone CodeEdit\n\nTap the **\"Fork\"** button on the top of the site. After forking clone the forked repository to your Mac.\n\n## Explore Issues\n\nFind issues from the [Issues tab](https://github.com/CodeEditApp/CodeEdit/issues) or from the To Do column in our [project](https://github.com/orgs/CodeEditApp/projects/3). If you find an issue you want to work on, please indicate it in the issue and/or attach a draft PR once available. An admin or maintainer will then assign the Issue and/or PR to you.\n\n> [!IMPORTANT]\n> Please make sure to first comment under an issue or ask a maintainer to assign you to the issue before working on it. This helps prevent multiple people from working on the same\n> thing, which could result in your work not being merged. Additionally, some issues might be reserved for those with more in-depth knowledge of the codebase.\n\n## Getting Started\n\nPlease read the [Getting Started](https://github.com/CodeEditApp/CodeEdit/wiki/Getting-Started) guide in our wiki.\n\nWe also have a [troubleshooting guide](https://github.com/CodeEditApp/CodeEdit/wiki/Troubleshooting) that provides common resolutions.\n\n## Code Style\n\nPlease read our guide on [Code Style](https://github.com/CodeEditApp/CodeEdit/wiki/Code-Style) in our wiki.\n\n## Pull Request\n\nOnce you are happy with your changes, submit a `Pull Request`.\n\nThe pull request opens with a template loaded. Fill out all fields that are relevant.\n\nThe `PR` should include following information:\n* A descriptive **title** on what changed.\n* A detailed **description** of changes.\n* If you made changes to the UI please add a **screenshot** or **video** as well.\n* If there is a related issue please add a **reference to the issue**. If not, create one beforehand and link it.\n* If your PR is still in progress mark it as **Draft**.\n\n### Checks, Tests & Documentation\n\nRequest a review from one of our admins @austincondiff, @lukepistrol, @MarcoCarnevali, @jasonplatts, @pkasila or maintainers @cstef, @linusS1, @RayZhao1998, @wdg.\n\n> [!TIP]\n> If it is your first PR, an admin will need to request a review for you.\n\n> [!IMPORTANT]\n> Please resolve all `Violation` errors in Xcode (except: _TODO:_ warnings). Otherwise the swiftlint check on GitHub will fail.\n\nOnce you submit the `PR` GitHub will run a couple of actions which run tests and `SwiftLint` (this can take a couple of minutes). Should a test fail, it cannot be merged until tests succeed.\n\nMake sure to resolve all merge-conflicts otherwise the `PR` cannot be merged.\n> [!IMPORTANT]\n> Make sure your code is well documented so others can interact with your code easily!\n"
  },
  {
    "path": "CodeEdit/AppDelegate.swift",
    "content": "//\n//  AppDelegate.swift\n//  CodeEdit\n//\n//  Created by Pavel Kasila on 12.03.22.\n//\n\nimport SwiftUI\nimport CodeEditSymbols\nimport CodeEditSourceEditor\nimport OSLog\n\n@MainActor\nfinal class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {\n    private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? \"\", category: \"AppDelegate\")\n    private let updater = SoftwareUpdater()\n\n    @Environment(\\.openWindow)\n    var openWindow\n\n    @LazyService var lspService: LSPService\n\n    func applicationDidFinishLaunching(_ notification: Notification) {\n        enableWindowSizeSaveOnQuit()\n        Settings.shared.preferences.general.appAppearance.applyAppearance()\n        checkForFilesToOpen()\n\n        NSApp.closeWindow(.welcome, .about)\n\n        DispatchQueue.main.async {\n            var needToHandleOpen = true\n\n            // If no windows were reopened by NSQuitAlwaysKeepsWindows, do default behavior.\n            // Non-WindowGroup SwiftUI Windows are still in NSApp.windows when they are closed,\n            // So we need to think about those.\n            if NSApp.windows.count > NSApp.openSwiftUIWindows {\n                needToHandleOpen = false\n            }\n\n            for index in 0..<CommandLine.arguments.count {\n                if CommandLine.arguments[index] == \"--open\" && (index + 1) < CommandLine.arguments.count {\n                    let path = CommandLine.arguments[index+1]\n                    let url = URL(fileURLWithPath: path)\n\n                    CodeEditDocumentController.shared.reopenDocument(\n                        for: url,\n                        withContentsOf: url,\n                        display: true\n                    ) { document, _, _ in\n                        document?.windowControllers.first?.synchronizeWindowTitleWithDocumentName()\n                    }\n\n                    needToHandleOpen = false\n                }\n            }\n\n            if needToHandleOpen {\n                self.handleOpen()\n            }\n        }\n    }\n\n    func applicationWillTerminate(_ aNotification: Notification) {\n\n    }\n\n    func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {\n        true\n    }\n\n    func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {\n        guard flag else {\n            handleOpen()\n            return false\n        }\n\n        /// Check if all windows are either miniaturized or not visible.\n        /// If so, attempt to find the first miniaturized window and deminiaturize it.\n        guard sender.windows.allSatisfy({ $0.isMiniaturized || !$0.isVisible }) else { return false }\n        sender.windows.first(where: { $0.isMiniaturized })?.deminiaturize(sender)\n        return false\n    }\n\n    func applicationShouldOpenUntitledFile(_ sender: NSApplication) -> Bool {\n        false\n    }\n\n    func handleOpen() {\n        let behavior = Settings.shared.preferences.general.reopenBehavior\n        switch behavior {\n        case .welcome:\n            if !tryFocusWindow(id: .welcome) {\n                openWindow(sceneID: .welcome)\n            }\n        case .openPanel:\n            CodeEditDocumentController.shared.openDocument(self)\n        case .newDocument:\n            CodeEditDocumentController.shared.newDocument(self)\n        }\n    }\n\n    /// Handle urls with the form `codeedit://file/{filepath}:{line}:{column}`\n    func application(_ application: NSApplication, open urls: [URL]) {\n        for url in urls {\n            let file = URL(fileURLWithPath: url.path).path.split(separator: \":\")\n            let filePath = URL(fileURLWithPath: String(file[0]))\n            let line = file.count > 1 ? Int(file[1]) ?? 0 : 0\n            let column = file.count > 2 ? Int(file[2]) ?? 1 : 1\n\n            CodeEditDocumentController.shared\n                .openDocument(withContentsOf: filePath, display: true) { document, _, error in\n                    if let error {\n                        NSAlert(error: error).runModal()\n                        return\n                    }\n                    if line > 0, let document = document as? CodeFileDocument {\n                        document.openOptions = CodeFileDocument.OpenOptions(\n                            cursorPositions: [CursorPosition(line: line, column: column > 0 ? column : 1)]\n                        )\n                    }\n                }\n        }\n    }\n\n    // MARK: - Should Terminate\n\n    /// Defers the application terminate message until we've finished cleanup.\n    ///\n    /// All paths _must_ call `NSApplication.shared.reply(toApplicationShouldTerminate: true)` as soon as possible.\n    ///\n    /// The two things needing deferring are:\n    /// - Language server cancellation\n    /// - Outstanding document changes.\n    ///\n    /// Things that don't need deferring (happen immediately):\n    /// - Task termination.\n    /// These are called immediately if no documents need closing, and are called by\n    /// ``documentController(_:didCloseAll:contextInfo:)`` if there are documents we need to defer for.\n    ///\n    /// See ``terminateLanguageServers()`` and ``documentController(_:didCloseAll:contextInfo:)`` for deferring tasks.\n    func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {\n        let projects: [String] = CodeEditDocumentController.shared.documents\n            .compactMap { ($0 as? WorkspaceDocument)?.fileURL?.path }\n\n        UserDefaults.standard.set(projects, forKey: AppDelegate.recoverWorkspacesKey)\n\n        let areAllDocumentsClean = CodeEditDocumentController.shared.documents.allSatisfy { !$0.isDocumentEdited }\n        guard areAllDocumentsClean else {\n            CodeEditDocumentController.shared.closeAllDocuments(\n                withDelegate: self,\n                didCloseAllSelector: #selector(documentController(_:didCloseAll:contextInfo:)),\n                contextInfo: nil\n            )\n            // `documentController(_:didCloseAll:contextInfo:)` will call `terminateLanguageServers()`\n            return .terminateLater\n        }\n\n        terminateTasks()\n        terminateLanguageServers()\n        return .terminateLater\n    }\n\n    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {\n        false\n    }\n\n    // MARK: - Open windows\n\n    @IBAction private func openWelcome(_ sender: Any) {\n        openWindow(sceneID: .welcome)\n    }\n\n    @IBAction private func openAbout(_ sender: Any) {\n        openWindow(sceneID: .about)\n    }\n\n    @IBAction func openFeedback(_ sender: Any) {\n        if tryFocusWindow(of: FeedbackView.self) { return }\n\n        FeedbackView().showWindow()\n    }\n\n    @IBAction private func checkForUpdates(_ sender: Any) {\n        updater.checkForUpdates()\n    }\n\n    /// Tries to focus a window with specified view content type.\n    /// - Parameter type: The type of viewContent which hosted in a window to be focused.\n    /// - Returns: `true` if window exist and focused, otherwise - `false`\n    private func tryFocusWindow<T: View>(of type: T.Type) -> Bool {\n        guard let window = NSApp.windows.filter({ ($0.contentView as? NSHostingView<T>) != nil }).first\n        else { return false }\n\n        window.makeKeyAndOrderFront(self)\n        return true\n    }\n\n    /// Tries to focus a window with specified sceneId\n    /// - Parameter type: Id of a window to be focused.\n    /// - Returns: `true` if window exist and focused, otherwise - `false`\n    private func tryFocusWindow(id: SceneID) -> Bool {\n        guard let window = NSApp.windows.filter({ $0.identifier?.rawValue == id.rawValue }).first\n        else { return false }\n\n        window.makeKeyAndOrderFront(self)\n        return true\n    }\n\n    // MARK: - Open With CodeEdit (Extension) functions\n    private func checkForFilesToOpen() {\n        guard let defaults = UserDefaults.init(\n            suiteName: \"app.codeedit.CodeEdit.shared\"\n        ) else {\n            print(\"Failed to get/init shared defaults\")\n            return\n        }\n\n        // Register enableOpenInCE (enable Open In CodeEdit\n        defaults.register(defaults: [\"enableOpenInCE\": true])\n\n        if let filesToOpen = defaults.string(forKey: \"openInCEFiles\") {\n            let files = filesToOpen.split(separator: \";\")\n\n            for filePath in files {\n                let fileURL = URL(fileURLWithPath: String(filePath))\n                CodeEditDocumentController.shared.reopenDocument(\n                    for: fileURL,\n                    withContentsOf: fileURL,\n                    display: true\n                ) { document, _, _ in\n                    document?.windowControllers.first?.synchronizeWindowTitleWithDocumentName()\n                }\n            }\n\n            defaults.removeObject(forKey: \"openInCEFiles\")\n        }\n\n        DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in\n            self?.checkForFilesToOpen()\n        }\n    }\n\n    /// Enable window size restoring on app relaunch after quitting.\n    private func enableWindowSizeSaveOnQuit() {\n        // This enables window restoring on normal quit (instead of only on force-quit).\n        UserDefaults.standard.setValue(true, forKey: \"NSQuitAlwaysKeepsWindows\")\n    }\n\n    // MARK: NSDocumentController delegate\n\n    @objc\n    func documentController(_ docController: NSDocumentController, didCloseAll: Bool, contextInfo: Any) {\n        if didCloseAll {\n            terminateTasks()\n            terminateLanguageServers()\n        }\n    }\n\n    /// Terminates running language servers. Used during app termination to ensure resources are freed.\n    private func terminateLanguageServers() {\n        Task { @MainActor in\n            let task = TaskNotificationModel(\n                id: \"appdelegate.terminate_language_servers\",\n                title: \"Stopping Language Servers\",\n                message: \"Stopping running language server processes...\",\n                isLoading: true\n            )\n\n            if !lspService.languageClients.isEmpty {\n                TaskNotificationHandler.postTask(action: .create, model: task)\n            }\n\n            try? await withTimeout(\n                duration: .seconds(2.0),\n                onTimeout: {\n                    // Stop-gap measure to ensure we don't hang on CMD-Q\n                    await self.lspService.killAllServers()\n                },\n                operation: {\n                    await self.lspService.stopAllServers()\n                }\n            )\n\n            TaskNotificationHandler.postTask(action: .delete, model: task)\n            NSApplication.shared.reply(toApplicationShouldTerminate: true)\n        }\n    }\n\n    /// Terminates all running tasks. Used during app termination to ensure resources are freed.\n    private func terminateTasks() {\n        let task = TaskNotificationModel(\n            id: \"appdelegate.terminate_tasks\",\n            title: \"Terminating Tasks\",\n            message: \"Interrupting all running tasks before quitting...\",\n            isLoading: true\n        )\n\n        let taskManagers = CodeEditDocumentController.shared.documents\n            .compactMap({ $0 as? WorkspaceDocument })\n            .compactMap({ $0.taskManager })\n\n        if taskManagers.reduce(0, { $0 + $1.activeTasks.count }) > 0 {\n            TaskNotificationHandler.postTask(action: .create, model: task)\n        }\n\n        taskManagers.forEach { manager in\n            manager.stopAllTasks()\n        }\n\n        TaskNotificationHandler.postTask(action: .delete, model: task)\n    }\n}\n\nextension AppDelegate {\n    static let recoverWorkspacesKey = \"recover.workspaces\"\n}\n"
  },
  {
    "path": "CodeEdit/Assets.xcassets/AccentColor.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "CodeEdit/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"CodeEdit-16.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"filename\" : \"CodeEdit-16@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"filename\" : \"CodeEdit-32.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"filename\" : \"CodeEdit-32@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"filename\" : \"CodeEdit-128.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"filename\" : \"CodeEdit-128@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"filename\" : \"CodeEdit-256.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"filename\" : \"CodeEdit-256@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"filename\" : \"CodeEdit-512.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"512x512\"\n    },\n    {\n      \"filename\" : \"CodeEdit-512@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"512x512\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "CodeEdit/Assets.xcassets/AppIconAlpha.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"CodeEditAlpha-16.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"filename\" : \"CodeEditAlpha-16@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"filename\" : \"CodeEditAlpha-32.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"filename\" : \"CodeEditAlpha-32@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"filename\" : \"CodeEditAlpha-128.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"filename\" : \"CodeEditAlpha-128@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"filename\" : \"CodeEditAlpha-256.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"filename\" : \"CodeEditAlpha-256@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"filename\" : \"CodeEditAlpha-512.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"512x512\"\n    },\n    {\n      \"filename\" : \"CodeEditAlpha-512@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"512x512\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "CodeEdit/Assets.xcassets/AppIconBeta.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"CodeEditBeta-16.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"filename\" : \"CodeEditBeta-16@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"filename\" : \"CodeEditBeta-32.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"filename\" : \"CodeEditBeta-32@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"filename\" : \"CodeEditBeta-128.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"filename\" : \"CodeEditBeta-128@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"filename\" : \"CodeEditBeta-256.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"filename\" : \"CodeEditBeta-256@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"filename\" : \"CodeEditBeta-512.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"512x512\"\n    },\n    {\n      \"filename\" : \"CodeEditBeta-512@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"512x512\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "CodeEdit/Assets.xcassets/AppIconDev.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"CodeEditDev-16.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"filename\" : \"CodeEditDev-16@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"filename\" : \"CodeEditDev-32.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"filename\" : \"CodeEditDev-32@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"filename\" : \"CodeEditDev-128.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"filename\" : \"CodeEditDev-128@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"filename\" : \"CodeEditDev-256.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"filename\" : \"CodeEditDev-256@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"filename\" : \"CodeEditDev-512.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"512x512\"\n    },\n    {\n      \"filename\" : \"CodeEditDev-512@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"512x512\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "CodeEdit/Assets.xcassets/AppIconPre.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"CodeEdit-Icon-Pre-16.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"filename\" : \"CodeEdit-Icon-Pre-16@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"filename\" : \"CodeEdit-Icon-Pre-32.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"filename\" : \"CodeEdit-Icon-Pre-32@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"filename\" : \"CodeEdit-Icon-Pre-128.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"filename\" : \"CodeEdit-Icon-Pre-128@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"filename\" : \"CodeEdit-Icon-Pre-256.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"filename\" : \"CodeEdit-Icon-Pre-256@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"filename\" : \"CodeEdit-Icon-Pre-512.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"512x512\"\n    },\n    {\n      \"filename\" : \"CodeEdit-Icon-Pre-512@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"512x512\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "CodeEdit/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "CodeEdit/Assets.xcassets/Custom Colors/Amber.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"color\" : {\n        \"color-space\" : \"display-p3\",\n        \"components\" : {\n          \"alpha\" : \"1.000\",\n          \"blue\" : \"0.133\",\n          \"green\" : \"0.635\",\n          \"red\" : \"0.784\"\n        }\n      },\n      \"idiom\" : \"universal\"\n    },\n    {\n      \"appearances\" : [\n        {\n          \"appearance\" : \"luminosity\",\n          \"value\" : \"dark\"\n        }\n      ],\n      \"color\" : {\n        \"color-space\" : \"display-p3\",\n        \"components\" : {\n          \"alpha\" : \"1.000\",\n          \"blue\" : \"0.302\",\n          \"green\" : \"0.812\",\n          \"red\" : \"0.961\"\n        }\n      },\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "CodeEdit/Assets.xcassets/Custom Colors/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "CodeEdit/Assets.xcassets/Custom Colors/CoolGray.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"color\" : {\n        \"color-space\" : \"display-p3\",\n        \"components\" : {\n          \"alpha\" : \"1.000\",\n          \"blue\" : \"0.553\",\n          \"green\" : \"0.541\",\n          \"red\" : \"0.518\"\n        }\n      },\n      \"idiom\" : \"universal\"\n    },\n    {\n      \"appearances\" : [\n        {\n          \"appearance\" : \"luminosity\",\n          \"value\" : \"dark\"\n        }\n      ],\n      \"color\" : {\n        \"color-space\" : \"display-p3\",\n        \"components\" : {\n          \"alpha\" : \"1.000\",\n          \"blue\" : \"0.549\",\n          \"green\" : \"0.537\",\n          \"red\" : \"0.525\"\n        }\n      },\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "CodeEdit/Assets.xcassets/Custom Colors/FolderBlue.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"color\" : {\n        \"color-space\" : \"display-p3\",\n        \"components\" : {\n          \"alpha\" : \"1.000\",\n          \"blue\" : \"0.973\",\n          \"green\" : \"0.698\",\n          \"red\" : \"0.125\"\n        }\n      },\n      \"idiom\" : \"universal\"\n    },\n    {\n      \"appearances\" : [\n        {\n          \"appearance\" : \"luminosity\",\n          \"value\" : \"dark\"\n        }\n      ],\n      \"color\" : {\n        \"color-space\" : \"display-p3\",\n        \"components\" : {\n          \"alpha\" : \"1.000\",\n          \"blue\" : \"0.859\",\n          \"green\" : \"0.698\",\n          \"red\" : \"0.365\"\n        }\n      },\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "CodeEdit/Assets.xcassets/Custom Colors/InspectorBackgroundColor.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"color\" : {\n        \"color-space\" : \"srgb\",\n        \"components\" : {\n          \"alpha\" : \"1.000\",\n          \"blue\" : \"0xF7\",\n          \"green\" : \"0xF5\",\n          \"red\" : \"0xF4\"\n        }\n      },\n      \"idiom\" : \"universal\"\n    },\n    {\n      \"appearances\" : [\n        {\n          \"appearance\" : \"luminosity\",\n          \"value\" : \"dark\"\n        }\n      ],\n      \"color\" : {\n        \"color-space\" : \"srgb\",\n        \"components\" : {\n          \"alpha\" : \"1.000\",\n          \"blue\" : \"0x25\",\n          \"green\" : \"0x1F\",\n          \"red\" : \"0x1C\"\n        }\n      },\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "CodeEdit/Assets.xcassets/Custom Colors/Scarlet.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"color\" : {\n        \"color-space\" : \"display-p3\",\n        \"components\" : {\n          \"alpha\" : \"1.000\",\n          \"blue\" : \"0.176\",\n          \"green\" : \"0.303\",\n          \"red\" : \"0.956\"\n        }\n      },\n      \"idiom\" : \"universal\"\n    },\n    {\n      \"appearances\" : [\n        {\n          \"appearance\" : \"luminosity\",\n          \"value\" : \"dark\"\n        }\n      ],\n      \"color\" : {\n        \"color-space\" : \"display-p3\",\n        \"components\" : {\n          \"alpha\" : \"1.000\",\n          \"blue\" : \"0.376\",\n          \"green\" : \"0.475\",\n          \"red\" : \"0.960\"\n        }\n      },\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "CodeEdit/Assets.xcassets/Custom Colors/Steel.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"color\" : {\n        \"color-space\" : \"display-p3\",\n        \"components\" : {\n          \"alpha\" : \"1.000\",\n          \"blue\" : \"0.522\",\n          \"green\" : \"0.463\",\n          \"red\" : \"0.373\"\n        }\n      },\n      \"idiom\" : \"universal\"\n    },\n    {\n      \"appearances\" : [\n        {\n          \"appearance\" : \"luminosity\",\n          \"value\" : \"dark\"\n        }\n      ],\n      \"color\" : {\n        \"color-space\" : \"display-p3\",\n        \"components\" : {\n          \"alpha\" : \"1.000\",\n          \"blue\" : \"0.749\",\n          \"green\" : \"0.690\",\n          \"red\" : \"0.585\"\n        }\n      },\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "CodeEdit/Assets.xcassets/Icons/BitBucketIcon.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"BitBucketIcon.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "CodeEdit/Assets.xcassets/Icons/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "CodeEdit/Assets.xcassets/Icons/GitHubIcon.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"GitHubIcon.png\",\n      \"idiom\" : \"universal\"\n    },\n    {\n      \"appearances\" : [\n        {\n          \"appearance\" : \"luminosity\",\n          \"value\" : \"dark\"\n        }\n      ],\n      \"filename\" : \"GitHubIcon-Dark.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "CodeEdit/Assets.xcassets/Icons/GitLabIcon.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"GitLabIcon.png\",\n      \"idiom\" : \"universal\"\n    },\n    {\n      \"appearances\" : [\n        {\n          \"appearance\" : \"luminosity\",\n          \"value\" : \"dark\"\n        }\n      ],\n      \"filename\" : \"GitLabIcon-Dark.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "CodeEdit/Assets.xcassets/line.3.horizontal.decrease.chevron.filled.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"line.3.horizontal.decrease.chevron.filled.pdf\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"preserves-vector-representation\" : true,\n    \"template-rendering-intent\" : \"template\"\n  }\n}\n"
  },
  {
    "path": "CodeEdit/Assets.xcassets/line.3.horizontal.decrease.chevron.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"line.3.horizontal.decrease.chevron.pdf\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"preserves-vector-representation\" : true,\n    \"template-rendering-intent\" : \"template\"\n  }\n}\n"
  },
  {
    "path": "CodeEdit/CodeEdit.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>com.apple.security.app-sandbox</key>\n\t<true/>\n\t<key>com.apple.security.files.user-selected.read-write</key>\n\t<true/>\n\t<key>com.apple.security.files.bookmarks.app-scope</key>\n\t<true/>\n\t<key>com.apple.security.network.client</key>\n\t<true/>\n\t<key>com.apple.security.application-groups</key>\n\t<array>\n\t\t<string>app.codeedit.CodeEdit.shared</string>\n\t\t<string>$(TeamIdentifierPrefix)</string>\n\t</array>\n</dict>\n</plist>\n"
  },
  {
    "path": "CodeEdit/CodeEditApp.swift",
    "content": "//\n//  CodeEditApp.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 11/03/2023.\n//\n\nimport SwiftUI\nimport WelcomeWindow\nimport AboutWindow\n\n@main\nstruct CodeEditApp: App {\n    @NSApplicationDelegateAdaptor var appdelegate: AppDelegate\n    @ObservedObject var settings = Settings.shared\n\n    let updater: SoftwareUpdater = SoftwareUpdater()\n\n    init() {\n        // Register singleton services before anything else\n        ServiceContainer.register(\n            LSPService()\n        )\n\n        _ = CodeEditDocumentController.shared\n        NSMenuItem.swizzle()\n        NSSplitViewItem.swizzle()\n    }\n\n    var body: some Scene {\n        Group {\n            WelcomeWindow(\n                subtitleView: { WelcomeSubtitleView() },\n                actions: { dismissWindow in\n                    NewFileButton(dismissWindow: dismissWindow)\n                    GitCloneButton(dismissWindow: dismissWindow)\n                    OpenFileOrFolderButton(dismissWindow: dismissWindow)\n                },\n                onDrop: { url, dismissWindow in\n                    Task {\n                        await CodeEditDocumentController.shared.openDocument(at: url, onCompletion: { dismissWindow() })\n                    }\n                }\n            )\n\n            ExtensionManagerWindow()\n\n            AboutWindow(\n                subtitleView: { AboutSubtitleView() },\n                actions: {\n                    AboutButton(title: \"Contributors\", destination: {\n                        ContributorsView()\n                    })\n                    AboutButton(title: \"Acknowledgements\", destination: {\n                        AcknowledgementsView()\n                    })\n                },\n                footer: { AboutFooterView() }\n            )\n\n            SettingsWindow()\n                .commands {\n                    CodeEditCommands()\n                }\n        }\n        .environment(\\.settings, settings.preferences) // Add settings to each window environment\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/About/AboutFooterView.swift",
    "content": "//\n//  AboutFooterView.swift\n//  CodeEdit\n//\n//  Created by Giorgi Tchelidze on 08.06.25.\n//\n\nimport SwiftUI\nimport AboutWindow\n\nstruct AboutFooterView: View {\n    var body: some View {\n        FooterView(\n            primaryView: {\n                Link(destination: URL(string: \"https://github.com/CodeEditApp/CodeEdit/blob/main/LICENSE.md\")!) {\n                    Text(\"MIT License\")\n                        .underline()\n                }\n            },\n            secondaryView: {\n                Text(Bundle.copyrightString ?? \"\")\n            }\n        )\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/About/AboutSubtitleView.swift",
    "content": "//\n//  AboutSubtitleView.swift\n//  CodeEdit\n//\n//  Created by Giorgi Tchelidze on 08.06.25.\n//\n\nimport SwiftUI\n\nstruct AboutSubtitleView: View {\n\n    @State private var didCopyVersion = false\n    @State private var isHoveringVersion = false\n\n    private var appVersion: String { Bundle.versionString ?? \"No Version\" }\n    private var appBuild: String { Bundle.buildString ?? \"No Build\" }\n    private var appVersionPostfix: String { Bundle.versionPostfix ?? \"\" }\n\n    var body: some View {\n        Text(\"Version \\(appVersion)\\(appVersionPostfix) (\\(appBuild))\")\n            .textSelection(.disabled)\n            .onTapGesture {\n                // Create a string suitable for pasting into a bug report\n                let macOSVersion = ProcessInfo.processInfo.operatingSystemVersion.semverString\n                NSPasteboard.general.clearContents()\n                NSPasteboard.general.setString(\n                    \"CodeEdit: \\(appVersion) (\\(appBuild))\\nmacOS: \\(macOSVersion)\",\n                    forType: .string\n                )\n                didCopyVersion.toggle()\n            }\n            .background(alignment: .leading) {\n                if isHoveringVersion {\n                    if #available(macOS 14.0, *) {\n                        Image(systemName: \"document.on.document.fill\")\n                            .font(.caption)\n                            .offset(x: -16, y: 0)\n                            .transition(.opacity)\n                            .symbolEffect(\n                                .bounce.down.wholeSymbol,\n                                options: .nonRepeating.speed(1.8),\n                                value: didCopyVersion\n                            )\n                    } else {\n                        Image(systemName: \"document.on.document.fill\")\n                            .font(.caption)\n                            .offset(x: -16, y: 0)\n                            .transition(.opacity)\n                    }\n                }\n            }\n            .onHover { hovering in\n                withAnimation(.easeInOut(duration: 0.1)) {\n                    isHoveringVersion = hovering\n                }\n            }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/About/Acknowledgements/ViewModels/AcknowledgementsViewModel.swift",
    "content": "//\n//  AcknowledgementsModel.swift\n//  CodeEditModules/Acknowledgements\n//\n//  Created by Lukas Pistrol on 01.05.22.\n//\n\nimport SwiftUI\n\nfinal class AcknowledgementsViewModel: ObservableObject {\n\n    @Published private(set) var acknowledgements: [AcknowledgementDependency]\n\n    var indexedAcknowledgements: [(index: Int, acknowledgement: AcknowledgementDependency)] {\n      return Array(zip(acknowledgements.indices, acknowledgements))\n    }\n\n    init(_ dependencies: [AcknowledgementDependency] = []) {\n        self.acknowledgements = dependencies\n\n        if acknowledgements.isEmpty {\n            fetchDependencies()\n        }\n    }\n\n    func fetchDependencies() {\n        self.acknowledgements.removeAll()\n        do {\n            if let bundlePath = Bundle.main.path(forResource: \"Package\", ofType: \"resolved\") {\n                let jsonData = try String(contentsOfFile: bundlePath).data(using: .utf8)\n                let parsedJSON = try JSONDecoder().decode(AcknowledgementObject.self, from: jsonData!)\n                for dependency in parsedJSON.pins.sorted(by: { $0.identity < $1.identity })\n                where dependency.identity.range(\n                    of: \"[Cc]ode[Ee]dit\",\n                    options: .regularExpression,\n                    range: nil,\n                    locale: nil\n                ) == nil {\n                    self.acknowledgements.append(\n                        AcknowledgementDependency(\n                            name: dependency.name,\n                            repositoryLink: dependency.location,\n                            version: dependency.state.version ?? \"-\"\n                        )\n                    )\n                }\n            }\n        } catch {\n            print(error)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/About/Acknowledgements/Views/AcknowledgementRowView.swift",
    "content": "//\n//  AcknowledgementsRowView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 1/19/23.\n//\n\nimport SwiftUI\n\nstruct AcknowledgementRowView: View {\n    @Environment(\\.openURL)\n    private var openURL\n\n    let acknowledgement: AcknowledgementDependency\n\n    var body: some View {\n        HStack {\n            Text(acknowledgement.name)\n                .font(.body)\n\n            Spacer()\n\n            Button {\n                openURL(acknowledgement.repositoryURL)\n            } label: {\n                Image(systemName: \"arrow.right.circle.fill\")\n                    .foregroundColor(Color(nsColor: .tertiaryLabelColor))\n            }\n            .buttonStyle(.plain)\n        }\n        .padding(.vertical, 12)\n        .frame(maxWidth: .infinity)\n    }\n}\n\nstruct AcknowledgementsRowView_Previews: PreviewProvider {\n    static var previews: some View {\n        AcknowledgementRowView(acknowledgement: AcknowledgementDependency(\n            name: \"Test\",\n            repositoryLink: \"https://www.test.com/\",\n            version: \"-\"\n        ))\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/About/Acknowledgements/Views/AcknowledgementsView.swift",
    "content": "//\n//  AcknowledgementsView.swift\n//  CodeEditModules/Acknowledgements\n//\n//  Created by Shivesh M M on 4/4/22.\n//\n\nimport SwiftUI\nimport AboutWindow\n\nstruct AcknowledgementsView: View {\n    @StateObject var model = AcknowledgementsViewModel()\n\n    var body: some View {\n        AboutDetailView(title: \"Acknowledgements\") {\n            LazyVStack(spacing: 0) {\n                ForEach(\n                    model.indexedAcknowledgements,\n                    id: \\.acknowledgement.name\n                ) { (index, acknowledgement) in\n                    if index != 0 {\n                        Divider()\n                            .frame(height: 0.5)\n                            .opacity(0.5)\n                    }\n                    AcknowledgementRowView(acknowledgement: acknowledgement)\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/About/Acknowledgements/Views/ParsePackagesResolved.swift",
    "content": "//\n//  ParsePackagesResolved.swift\n//  CodeEditModules/Acknowledgements\n//\n//  Created by Shivesh M M on 4/4/22.\n//\n\nimport Foundation\n\nstruct AcknowledgementDependency: Decodable {\n    var name: String\n    var repositoryLink: String\n    var version: String\n    var repositoryURL: URL {\n        URL(string: repositoryLink)!\n    }\n}\n\n// MARK: - Object\nstruct AcknowledgementObject: Codable {\n    let pins: [AcknowledgementPin]\n}\n\n// MARK: - Pin\nstruct AcknowledgementPin: Codable {\n    let identity: String\n    let location: String\n    let state: AcknowledgementPackageState\n\n    var name: String {\n        location.split(separator: \"/\").last?.replacingOccurrences(of: \".git\", with: \"\") ?? identity\n    }\n}\n\n// MARK: - State\nstruct AcknowledgementPackageState: Codable {\n    let revision: String\n    let version: String?\n}\n"
  },
  {
    "path": "CodeEdit/Features/About/BlurButtonStyle.swift",
    "content": "//\n//  BlurButtonStyle.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 21/01/2023.\n//\n\nimport SwiftUI\n\nextension ButtonStyle where Self == BlurButtonStyle {\n    static var blur: BlurButtonStyle { BlurButtonStyle() }\n    static var secondaryBlur: BlurButtonStyle { BlurButtonStyle(isSecondary: true) }\n}\n\nstruct BlurButtonStyle: ButtonStyle {\n    var isSecondary: Bool = false\n\n    @Environment(\\.controlSize)\n    var controlSize\n\n    @Environment(\\.colorScheme)\n    var colorScheme\n\n    var height: CGFloat {\n        switch controlSize {\n        case .large:\n            return 28\n        default:\n            return 20\n        }\n    }\n\n    func makeBody(configuration: Configuration) -> some View {\n        configuration.label\n            .padding(.horizontal, 8)\n            .frame(height: height)\n            .background {\n                switch colorScheme {\n                case .dark:\n                    ZStack {\n                        Color.gray.opacity(0.001)\n                        if isSecondary {\n                            Rectangle()\n                                .fill(.regularMaterial)\n                        } else {\n                            Rectangle()\n                                .fill(.regularMaterial)\n                                .blendMode(.plusLighter)\n                        }\n                        Color.gray.opacity(isSecondary ? 0.10 : 0.30)\n                        Color.white.opacity(configuration.isPressed ? 0.10 : 0.00)\n                    }\n                case .light:\n                    ZStack {\n                        Color.gray.opacity(0.001)\n                        Rectangle()\n                            .fill(.regularMaterial)\n                            .blendMode(.darken)\n                        Color.gray.opacity(isSecondary ? 0.05 : 0.15)\n                            .blendMode(.plusDarker)\n                        Color.gray.opacity(configuration.isPressed ? 0.10 : 0.00)\n                    }\n                @unknown default:\n                    Color.black\n                }\n            }\n            .clipShape(RoundedRectangle(cornerRadius: controlSize == .large ? 6 : 5))\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/About/Contributors/ContributorRowView.swift",
    "content": "//\n//  ContributorRowView.swift\n//  CodeEdit\n//\n//  Created by Lukas Pistrol on 19.01.23.\n//\n\nimport SwiftUI\nimport CodeEditSymbols\n\nstruct ContributorRowView: View {\n\n    let contributor: Contributor\n\n    var body: some View {\n        HStack {\n            userImage\n            VStack(alignment: .leading, spacing: 2) {\n                HStack {\n                    Text(contributor.name)\n                        .font(.headline)\n                }\n                HStack(spacing: 3) {\n                    ForEach(contributor.contributions, id: \\.self) { item in\n                        tag(item)\n                    }\n                }\n            }\n            Spacer()\n            HStack(alignment: .top) {\n                if let profileURL = contributor.profileURL, profileURL != contributor.gitHubURL {\n                    ActionButton(url: profileURL, image: .init(systemName: \"globe\"))\n                }\n                if let gitHubURL = contributor.gitHubURL {\n                    ActionButton(url: gitHubURL, image: .github)\n                }\n            }\n        }\n        .padding(.vertical, 8)\n    }\n\n    private var userImage: some View {\n        AsyncImage(url: contributor.avatarURL) { image in\n            image\n                .resizable()\n                .frame(width: 32, height: 32)\n                .clipShape(Circle())\n                .help(contributor.name)\n        } placeholder: {\n            Image(systemName: \"person.circle.fill\")\n                .resizable()\n                .frame(width: 32, height: 32)\n                .help(contributor.name)\n        }\n    }\n\n    private func tag(_ item: Contributor.Contribution) -> some View {\n        Text(item.rawValue.capitalized)\n            .font(.caption)\n            .padding(.horizontal, 6)\n            .padding(.vertical, 1)\n            .foregroundColor(item.color)\n            .background {\n                Capsule(style: .continuous)\n                    .strokeBorder(lineWidth: 1)\n                    .foregroundStyle(item.color)\n                    .opacity(0.8)\n            }\n    }\n\n    private struct ActionButton: View {\n        @Environment(\\.openURL)\n        private var openURL\n        @State private var hovering = false\n\n        let url: URL\n        let image: Image\n\n        var body: some View {\n            Button {\n                openURL(url)\n            } label: {\n                image\n                    .imageScale(.medium)\n                    .foregroundColor(hovering ? .primary : .secondary)\n            }\n            .buttonStyle(.plain)\n            .onHover { hover in\n                hovering = hover\n            }\n        }\n    }\n}\n\nstruct ContributorRowView_Previews: PreviewProvider {\n    static var previews: some View {\n        let contributor = Contributor(\n            login: \"lukepistrol\",\n            name: \"Lukas Pistrol\",\n            avatarURLString: \"https://avatars.githubusercontent.com/u/9460130?v=4\",\n            profile: \"http://lukaspistrol.com\",\n            contributions: [.infra, .test, .code]\n        )\n        ContributorRowView(contributor: contributor)\n            .frame(width: 350)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/About/Contributors/ContributorsView.swift",
    "content": "//\n//  ContributorsView.swift\n//  CodeEdit\n//\n//  Created by Lukas Pistrol on 19.01.23.\n//\n\nimport SwiftUI\nimport AboutWindow\n\nstruct ContributorsView: View {\n    @StateObject var model = ContributorsViewModel()\n\n    var body: some View {\n        AboutDetailView(title: \"Contributors\") {\n            LazyVStack(spacing: 0) {\n                ForEach(model.contributors) { contributor in\n                    ContributorRowView(contributor: contributor)\n                    Divider()\n                        .frame(height: 0.5)\n                        .opacity(0.5)\n                }\n            }\n        }\n    }\n}\n\nclass ContributorsViewModel: ObservableObject {\n    @Published private(set) var contributors: [Contributor] = []\n\n    init() {\n        guard let url = Bundle.main.url(\n            forResource: \".all-contributorsrc\",\n            withExtension: nil\n        ) else { return }\n        do {\n            let data = try Data(contentsOf: url)\n            let root = try JSONDecoder().decode(ContributorsRoot.self, from: data)\n            self.contributors = root.contributors\n        } catch {\n            print(error)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/About/Contributors/Model/Contributor.swift",
    "content": "//\n//  Contributor.swift\n//  CodeEdit\n//\n//  Created by Lukas Pistrol on 19.01.23.\n//\n\nimport SwiftUI\n\nstruct ContributorsRoot: Codable {\n    var contributors: [Contributor]\n}\n\nstruct Contributor: Codable, Identifiable {\n    var id: String { login }\n    var login: String\n    var name: String\n    var avatarURLString: String\n    var profile: String\n    var contributions: [Contribution]\n\n    var avatarURL: URL? {\n        URL(string: avatarURLString)\n    }\n\n    var gitHubURL: URL? {\n        URL(string: \"https://github.com/\\(login)\")\n    }\n\n    var profileURL: URL? {\n        URL(string: profile)\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case login, name, profile, contributions\n        case avatarURLString = \"avatar_url\"\n    }\n\n    enum Contribution: String, Codable {\n        case design, code, infra, test, bug, maintenance, plugin\n\n        var color: Color {\n            switch self {\n            case .design: return .blue\n            case .code: return .indigo\n            case .infra: return .pink\n            case .test: return .purple\n            case .bug: return .red\n            case .maintenance: return .brown\n            case .plugin: return .gray\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/ActivityViewer/ActivityViewer.swift",
    "content": "//\n//  ActivityViewer.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 21.06.24.\n//\n\nimport SwiftUI\n\n/// A view that shows the activity bar and the current status of any executed task\nstruct ActivityViewer: View {\n    @Environment(\\.colorScheme)\n    var colorScheme\n\n    var workspaceFileManager: CEWorkspaceFileManager?\n\n    @ObservedObject var taskNotificationHandler: TaskNotificationHandler\n    @ObservedObject var workspaceSettingsManager: CEWorkspaceSettings\n\n    // TODO: try to get this from the envrionment\n    @ObservedObject var taskManager: TaskManager\n\n    init(\n        workspaceFileManager: CEWorkspaceFileManager?,\n        workspaceSettingsManager: CEWorkspaceSettings,\n        taskNotificationHandler: TaskNotificationHandler,\n        taskManager: TaskManager\n    ) {\n        self.workspaceFileManager = workspaceFileManager\n        self.workspaceSettingsManager = workspaceSettingsManager\n        self.taskNotificationHandler = taskNotificationHandler\n        self.taskManager = taskManager\n    }\n    var body: some View {\n        Group {\n            if #available(macOS 26, *) {\n                content\n                    .fixedSize(horizontal: false, vertical: false)\n                    .padding(5)\n                // TaskNotificationView doesn't have it's own padding\n                // Also this padding seems weird. However, we want the spinning circle to be padded with the same\n                // amount from both the top and bottom, as well as the trailing edge. So despite it not being exactly\n                // the same it *looks* correctly padded\n                    .padding(.trailing, 5)\n                    .clipShape(Capsule())\n                    .frame(minWidth: 200)\n            } else {\n                content\n                    .fixedSize(horizontal: false, vertical: false)\n                    .padding(.horizontal, 5)\n                    .padding(.vertical, 1.5)\n                    .frame(height: 22)\n                    .clipped()\n                    .background {\n                        if colorScheme == .dark {\n                            RoundedRectangle(cornerRadius: 5)\n                                .opacity(0.1)\n                        } else {\n                            RoundedRectangle(cornerRadius: 5)\n                                .opacity(0.1)\n                        }\n                    }\n            }\n        }\n        .accessibilityElement(children: .contain)\n        .accessibilityLabel(\"Activity Viewer\")\n    }\n\n    @ViewBuilder private var content: some View {\n        HStack(spacing: 0) {\n            SchemeDropDownView(\n                workspaceSettingsManager: workspaceSettingsManager,\n                workspaceFileManager: workspaceFileManager\n            )\n\n            TaskDropDownView(taskManager: taskManager)\n\n            Spacer(minLength: 0)\n\n            TaskNotificationView(taskNotificationHandler: taskNotificationHandler)\n                .fixedSize()\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/ActivityViewer/Models/TaskNotificationModel.swift",
    "content": "//\n//  TaskNotificationModel.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 21.06.24.\n//\n\nimport Foundation\n\n/// Represents a notifications or tasks, that are displayed in the activity viewer\nstruct TaskNotificationModel: Equatable {\n    var id: String\n    var title: String\n    var message: String?\n    var percentage: Double?\n    var isLoading: Bool = false\n}\n"
  },
  {
    "path": "CodeEdit/Features/ActivityViewer/Notifications/CECircularProgressView.swift",
    "content": "//\n//  CECircularProgressView.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 21.06.24.\n//\n\nimport SwiftUI\n\nstruct CECircularProgressView: View {\n    @State private var isAnimating = false\n    @State private var previousValue: Bool = false\n\n    var progress: Double?\n    var currentTaskCount: Int = 1\n\n    let lineWidth: CGFloat = 2\n\n    var body: some View {\n        Circle()\n            .stroke(style: StrokeStyle(lineWidth: lineWidth))\n            .foregroundStyle(.tertiary)\n            .overlay {\n                if let progress = progress {\n                    Circle()\n                        .trim(from: 0, to: progress)\n                        .stroke(Color.accentColor, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round))\n                        .animation(.easeInOut, value: progress)\n                } else {\n                    Circle()\n                        .trim(from: 0, to: 0.5)\n                        .stroke(Color.accentColor, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round))\n                        .rotationEffect(\n                            previousValue ?\n                                .degrees(isAnimating ?  0 : -360)\n                            : .degrees(isAnimating ? 360 : 0)\n                        )\n                        .animation(Animation.linear(duration: 1).repeatForever(autoreverses: false), value: isAnimating)\n                        .onAppear {\n                            self.previousValue = isAnimating\n                            self.isAnimating.toggle()\n                        }\n                }\n            }\n            .rotationEffect(.degrees(-90))\n            .padding(lineWidth/2)\n            .overlay {\n                if currentTaskCount > 1 {\n                    Text(\"\\(currentTaskCount)\")\n                        .font(.caption)\n                }\n            }\n            .accessibilityElement()\n            .accessibilityAddTraits(.updatesFrequently)\n            .accessibilityValue(\n                progress != nil ? Text(progress!, format: .percent) : Text(\"working\")\n            )\n    }\n}\n\n#Preview {\n    Group {\n        CECircularProgressView(currentTaskCount: 1)\n            .frame(width: 22, height: 22)\n\n        CECircularProgressView(progress: 0.65, currentTaskCount: 1)\n            .frame(width: 22, height: 22)\n    }\n    .padding()\n}\n"
  },
  {
    "path": "CodeEdit/Features/ActivityViewer/Notifications/TaskNotificationHandler.swift",
    "content": "//\n//  TaskNotificationHandler.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 21.06.24.\n//\n\nimport Foundation\nimport Combine\n\n/// Manages task-related notifications.\n///\n/// This class listens for notifications named `.taskNotification` and performs actions\n/// such as creating, updating, or deleting tasks based on the notification's content.\n///\n/// When a task is created, it is added to the end of the array. The activity viewer displays\n/// only the first item in the array. To immediately display a notification, use the \n/// `\"action\": \"createWithPriority\"` option to insert the task at the beginning of the array.\n/// *Note: This option should be reserved for important notifications only.*\n///\n/// It is recommended to use `UUID().uuidString` to generate a unique identifier for each task.\n/// This identifier can then be used to update or delete the task. Alternatively, you can use any\n/// unique identifier, such as a token sent from a language server.\n///\n/// Remember to manage your task notifications appropriately. You should either delete task \n/// notifications manually or schedule their deletion in advance using the `deleteWithDelay` method.\n///\n/// Some tasks should be restricted to a specific workspace. To do this, specify the `workspace` attribute in the\n/// notification's `userInfo` dictionary as a `URL`, or use the `toWorkspace` parameter on\n/// ``TaskNotificationHandler/postTask(toWorkspace:action:model:)``.\n///\n/// ## Available Methods\n/// - `create`:\n///     Creates a new Task Notification. \n///     Required fields: `id` (String), `action` (String), `title` (String). \n///     Optional fields: `message` (String), `percentage` (Double), `isLoading` (Bool), `workspace` (URL).\n/// - `createWithPriority`:\n///     Creates a new Task Notification and inserts it at the start of the array.\n///     This ensures it appears in the activity viewer even if there are other task notifications before it.\n///     **Note:** This should only be used for important notifications!\n///     Required fields: `id` (String), `action` (String), `title` (String). \n///     Optional fields: `message` (String), `percentage` (Double), `isLoading` (Bool), `workspace` (URL).\n/// - `update`:\n///     Updates an existing task notification. It's important to pass the same `id` to update the correct task.\n///     Required fields: `id` (String), `action` (String). \n///     Optional fields: `title` (String), `message` (String), `percentage` (Double), `isLoading` (Bool),\n///     `workspace` (URL).\n/// - `delete`:\n///     Deletes an existing task notification.\n///     Required fields: `id` (String), `action` (String).\n///     Optional field: `workspace` (URL).\n/// - `deleteWithDelay`:\n///     Deletes an existing task notification after a certain `TimeInterval`.\n///     Required fields: `id` (String), `action` (String), `delay` (Double).\n///     Optional field: `workspace` (URL).\n///     **Important:** When specifying the delay, ensure it's a double.\n///     For example, '2' would be invalid because it would count as an integer, use '2.0' instead.\n///\n/// ## Example Usage:\n/// ```swift\n/// let uuidString = UUID().uuidString\n///\n/// func createTask() {\n///     let userInfo: [String: Any] = [\n///         \"id\": \"uniqueTaskID\",\n///         \"action\": \"create\",\n///         \"title\": \"Task Title\"\n///     ]\n///     NotificationCenter.default.post(name: .taskNotification, object: nil, userInfo: userInfo)\n/// }\n///\n/// func createTaskWithPriority() {\n///     let userInfo: [String: Any] = [\n///         \"id\": \"uniqueTaskID\",\n///         \"action\": \"createWithPriority\",\n///         \"title\": \"Priority Task Title\"\n///     ]\n///     NotificationCenter.default.post(name: .taskNotification, object: nil, userInfo: userInfo)\n/// }\n///\n/// func updateTask() {\n///     var userInfo: [String: Any] = [\n///         \"id\": \"uniqueTaskID\",\n///         \"action\": \"update\",\n///         \"title\": \"Updated Task Title\",\n///         \"message\": \"Updated Task Message\",\n///         \"percentage\": 0.5,\n///         \"isLoading\": true\n///     ]\n///     NotificationCenter.default.post(name: .taskNotification, object: nil, userInfo: userInfo)\n/// }\n///\n/// func deleteTask() {\n///     let userInfo: [String: Any] = [\n///         \"id\": \"uniqueTaskID\",\n///         \"action\": \"delete\"\n///     ]\n///     NotificationCenter.default.post(name: .taskNotification, object: nil, userInfo: userInfo)\n/// }\n///\n/// func deleteTaskWithDelay() {\n///     let userInfo: [String: Any] = [\n///         \"id\": \"uniqueTaskID\",\n///         \"action\": \"deleteWithDelay\",\n///         \"delay\": 4.0 // 4 would be invalid, because it would count as an int\n///     ]\n///     NotificationCenter.default.post(name: .taskNotification, object: nil, userInfo: userInfo)\n/// }\n/// ```\n///\n/// You can also use the static helper method instead of creating dictionaries manually:\n/// ```swift\n/// TaskNotificationHandler.postTask(action: .create, model: .init(id: \"task_id\", \"title\": \"New Task\"))\n/// ```\n///\n/// - Important: Please refer to ``CodeEdit/TaskNotificationModel`` and ensure you pass the correct values.\nfinal class TaskNotificationHandler: ObservableObject {\n    @Published private(set) var notifications: [TaskNotificationModel] = []\n    var workspaceURL: URL?\n    var cancellables: Set<AnyCancellable> = []\n\n    enum Action: String {\n        case create\n        case createWithPriority\n        case update\n        case delete\n        case deleteWithDelay\n    }\n\n    /// Post a new task.\n    /// - Parameters:\n    ///   - toWorkspace: The workspace to restrict the task to. Defaults to `nil`, which is received by all workspaces.\n    ///   - action: The action being taken on the task.\n    ///   - model: The task contents.\n    @MainActor\n    static func postTask(toWorkspace: URL? = nil, action: Action, model: TaskNotificationModel) {\n        NotificationCenter.default.post(name: .taskNotification, object: nil, userInfo: [\n            \"id\": model.id,\n            \"title\": model.title,\n            \"message\": model.message as Any,\n            \"percentage\": model.percentage as Any,\n            \"isLoading\": model.isLoading,\n            \"action\": action.rawValue,\n            \"workspace\": toWorkspace as Any\n        ])\n    }\n\n    /// Initialises a new `TaskNotificationHandler` and starts observing for task notifications.\n    init(workspaceURL: URL? = nil) {\n        self.workspaceURL = workspaceURL\n\n        NotificationCenter.default\n            .publisher(for: .taskNotification)\n            .receive(on: DispatchQueue.main)\n            .sink { notification in\n                self.handleNotification(notification)\n            }\n            .store(in: &cancellables)\n    }\n\n    deinit {\n        NotificationCenter.default.removeObserver(self, name: .taskNotification, object: nil)\n    }\n\n    /// Handles notifications about task events.\n    ///\n    /// - Parameter notification: The notification containing task information.\n    private func handleNotification(_ notification: Notification) {\n        guard let userInfo = notification.userInfo,\n              let taskID = userInfo[\"id\"] as? String,\n              let actionRaw = userInfo[\"action\"] as? String,\n              let action = Action(rawValue: actionRaw) else { return }\n\n        // If a workspace is specified and doesn't match, don't do anything with this task.\n        if let workspaceURL = userInfo[\"workspace\"] as? URL, workspaceURL != self.workspaceURL {\n            return\n        }\n\n        switch action {\n        case .create, .createWithPriority:\n            createTask(task: userInfo)\n        case .update:\n            updateTask(task: userInfo)\n        case .delete:\n            deleteTask(taskID: taskID)\n        case .deleteWithDelay:\n            if let delay = userInfo[\"delay\"] as? Double {\n                deleteTaskAfterDelay(taskID: taskID, delay: delay)\n            }\n        }\n    }\n\n    /// Creates a new task or inserts it at the beginning of the tasks array based on the action.\n    ///\n    /// - Parameter task: A dictionary containing task information.\n    private func createTask(task: [AnyHashable: Any]) {\n        guard let title = task[\"title\"] as? String,\n              let id = task[\"id\"] as? String,\n              let action = task[\"action\"] as? String else {\n            return\n        }\n\n        let task = TaskNotificationModel(\n            id: id,\n            title: title,\n            message: task[\"message\"] as? String,\n            percentage: task[\"percentage\"] as? Double,\n            isLoading: task[\"isLoading\"] as? Bool ?? false\n        )\n\n        if action == \"create\" {\n            notifications.append(task)\n        } else {\n            notifications.insert(task, at: 0)\n        }\n    }\n\n    /// Updates an existing task with new information.\n    ///\n    /// - Parameter task: A dictionary containing task information.\n    private func updateTask(task: [AnyHashable: Any]) {\n        guard let taskID = task[\"id\"] as? String else { return }\n            if let index = self.notifications.firstIndex(where: { $0.id == taskID }) {\n                if let title = task[\"title\"] as? String {\n                    self.notifications[index].title = title\n                }\n                if let message = task[\"message\"] as? String {\n                    self.notifications[index].message = message\n                }\n                if let percentage = task[\"percentage\"] as? Double {\n                    self.notifications[index].percentage = percentage\n                }\n                if let isLoading = task[\"isLoading\"] as? Bool {\n                    self.notifications[index].isLoading = isLoading\n                }\n            }\n    }\n\n    private func deleteTask(taskID: String) {\n            self.notifications.removeAll { $0.id == taskID }\n    }\n\n    private func deleteTaskAfterDelay(taskID: String, delay: Double) {\n        DispatchQueue.main.asyncAfter(deadline: .now() + delay) {\n            self.notifications.removeAll { $0.id == taskID }\n        }\n    }\n}\n\nextension Notification.Name {\n    static let taskNotification = Notification.Name(\"taskNotification\")\n}\n"
  },
  {
    "path": "CodeEdit/Features/ActivityViewer/Notifications/TaskNotificationView.swift",
    "content": "//\n//  TaskNotificationView.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 21.06.24.\n//\n\nimport SwiftUI\n\nstruct TaskNotificationView: View {\n    @Environment(\\.controlActiveState)\n    private var activeState\n\n    @ObservedObject var taskNotificationHandler: TaskNotificationHandler\n    @State private var isPresented: Bool = false\n    @State var notification: TaskNotificationModel?\n\n    var body: some View {\n        ZStack {\n            HStack {\n                if let notification {\n                    HStack {\n                        Text(notification.title)\n                            .font(.subheadline)\n                            .transition(\n                                .asymmetric(insertion: .move(edge: .top), removal: .move(edge: .bottom))\n                                .combined(with: .opacity)\n                            )\n                            .id(\"NotificationTitle\" + notification.title)\n                    }\n                    .transition(.opacity.combined(with: .move(edge: .trailing)))\n\n                    loaderView(notification: notification)\n                        .transition(.opacity)\n                        .id(\"Loader\")\n                } else {\n                    Text(\"\")\n                        .id(\"Loader\")\n                }\n            }\n            .opacity(activeState == .inactive ? 0.4 : 1.0)\n            .padding(3)\n            .padding(-3)\n            .popover(isPresented: $isPresented, arrowEdge: .bottom) {\n                TaskNotificationsDetailView(taskNotificationHandler: taskNotificationHandler)\n            }\n            .onTapGesture {\n                self.isPresented.toggle()\n            }\n        }\n        .animation(.easeInOut, value: notification)\n        .onChange(of: taskNotificationHandler.notifications) { _, newValue in\n            withAnimation {\n                notification = newValue.first\n            }\n        }\n    }\n\n    @ViewBuilder\n    private func loaderView(notification: TaskNotificationModel) -> some View {\n        if notification.isLoading {\n            CECircularProgressView(\n                progress: notification.percentage,\n                currentTaskCount: taskNotificationHandler.notifications.count\n            )\n            .if(.tahoe) {\n                $0.padding(.leading, 1)\n            } else: {\n                $0.padding(.horizontal, -1)\n            }\n            .frame(height: 16)\n        } else {\n            if taskNotificationHandler.notifications.count > 1 {\n                Text(\"\\(taskNotificationHandler.notifications.count)\")\n                    .font(.caption)\n                    .padding(5)\n                    .background(\n                        Circle()\n                            .foregroundStyle(.gray)\n                            .opacity(0.2)\n                    )\n                    .padding(-5)\n            }\n        }\n    }\n}\n\n#Preview {\n    TaskNotificationView(taskNotificationHandler: TaskNotificationHandler())\n}\n"
  },
  {
    "path": "CodeEdit/Features/ActivityViewer/Notifications/TaskNotificationsDetailView.swift",
    "content": "//\n//  TaskNotificationsDetailView.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 21.06.24.\n//\n\nimport SwiftUI\n\nstruct TaskNotificationsDetailView: View {\n    @ObservedObject var taskNotificationHandler: TaskNotificationHandler\n    @State private var selectedTaskNotificationIndex: Int = 0\n    var body: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 15) {\n                ForEach(taskNotificationHandler.notifications, id: \\.id) { notification in\n                    HStack(alignment: .center, spacing: 8) {\n                        CECircularProgressView(progress: notification.percentage)\n                            .frame(width: 16, height: 16)\n                        VStack(alignment: .leading) {\n                            Text(notification.title)\n                                .fixedSize(horizontal: false, vertical: true)\n                                .transition(.identity)\n\n                            if let message = notification.message, !message.isEmpty {\n                                Text(message)\n                                    .font(.subheadline)\n                                    .foregroundStyle(.secondary)\n                            }\n                        }\n                        Spacer()\n                    }\n                }\n            }\n        }\n        .padding(15)\n        .frame(minWidth: 320)\n        .onChange(of: taskNotificationHandler.notifications) { _, newValue in\n            if selectedTaskNotificationIndex >= newValue.count {\n                selectedTaskNotificationIndex = 0\n            }\n        }\n    }\n}\n\n#Preview {\n    TaskNotificationsDetailView(taskNotificationHandler: TaskNotificationHandler())\n}\n"
  },
  {
    "path": "CodeEdit/Features/ActivityViewer/Tasks/ActiveTaskView.swift",
    "content": "//\n//  ActiveTaskView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 8/4/24.\n//\n\nimport SwiftUI\n\n// We need to observe each active task individually because:\n// 1. Active tasks are nested inside TaskManager.\n// 2. Reference types (like objects) do not notify observers when their internal state changes.\n/// `ActiveTaskView` represents a single active task and observes its state.\n/// - Parameter activeTask: The active task to be displayed and observed.\nstruct ActiveTaskView: View {\n    @ObservedObject var activeTask: CEActiveTask\n\n    var body: some View {\n        TaskView(task: activeTask.task, status: activeTask.status)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/ActivityViewer/Tasks/DropdownMenuItemStyleModifier.swift",
    "content": "//\n//  DropdownMenuItemStyleModifier.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 24.06.24.\n//\n\nimport SwiftUI\n\nextension View {\n    @ViewBuilder\n    func dropdownItemStyle() -> some View {\n        self.modifier(DropdownMenuItemStyleModifier())\n    }\n}\n\nstruct DropdownMenuItemStyleModifier: ViewModifier {\n    @State private var isHovering = false\n\n    func body(content: Content) -> some View {\n        content\n            .padding(.vertical, 4)\n            .padding(.horizontal, 8)\n            .background(\n                isHovering\n                ? AnyView(EffectView(.selection, blendingMode: .withinWindow, emphasized: true))\n                    : AnyView(Color.clear)\n            )\n            .foregroundColor(isHovering ? Color(NSColor.white) : .primary)\n            .if(.tahoe) {\n                if #available(macOS 26, *) {\n                    $0.clipShape(ContainerRelativeShape())\n                }\n            } else: {\n                $0.clipShape(RoundedRectangle(cornerRadius: 5))\n            }\n            .onHover(perform: { hovering in\n                self.isHovering = hovering\n            })\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/ActivityViewer/Tasks/OptionMenuItemView.swift",
    "content": "//\n//  OptionMenuItemView.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 24.06.24.\n//\n\nimport SwiftUI\n\nstruct OptionMenuItemView: View {\n    var label: String\n    var action: () -> Void\n\n    var body: some View {\n        HStack {\n            Text(label)\n            Spacer()\n        }\n        .padding(.horizontal, 20)\n        .dropdownItemStyle()\n        .onTapGesture {\n            action()\n        }\n        .accessibilityElement()\n        .accessibilityAction {\n            action()\n        }\n        .accessibilityLabel(label)\n    }\n}\n\n#Preview {\n    OptionMenuItemView(label: \"Test\") {\n        print(\"test\")\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/ActivityViewer/Tasks/SchemeDropDownView.swift",
    "content": "//\n//  SchemeDropDownView.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 24.06.24.\n//\n\nimport SwiftUI\n\nstruct SchemeDropDownView: View {\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @Environment(\\.controlActiveState)\n    private var activeState\n\n    @State var isSchemePopOverPresented: Bool = false\n    @State private var isHoveringScheme: Bool = false\n\n    @ObservedObject var workspaceSettingsManager: CEWorkspaceSettings\n    var workspaceFileManager: CEWorkspaceFileManager?\n\n    var workspaceName: String {\n        workspaceSettingsManager.settings.project.projectName\n    }\n\n    /// Resolves the name one step further than `workspaceName`.\n    var workspaceDisplayName: String {\n        workspaceName.isEmpty\n        ? (workspaceFileManager?.workspaceItem.fileName() ?? \"No Project found\")\n        : workspaceName\n    }\n\n    var body: some View {\n        Group {\n            if #available(macOS 26, *) {\n                tahoe\n            } else {\n                seqouia\n            }\n        }\n        .onHover(perform: { hovering in\n            self.isHoveringScheme = hovering\n        })\n        .instantPopover(isPresented: $isSchemePopOverPresented, arrowEdge: .top) {\n            popoverContent\n        }\n        .onTapGesture {\n            isSchemePopOverPresented.toggle()\n        }\n        .accessibilityElement(children: .combine)\n        .accessibilityAddTraits(.isButton)\n        .accessibilityIdentifier(\"SchemeDropdown\")\n        .accessibilityValue(workspaceDisplayName)\n        .accessibilityLabel(\"Active Scheme\")\n        .accessibilityHint(\"Open the active scheme menu\")\n        .accessibilityAction {\n            isSchemePopOverPresented.toggle()\n        }\n    }\n\n    @available(macOS 26, *)\n    @ViewBuilder private var tahoe: some View {\n        HStack(spacing: 4) {\n            label\n            chevron\n                .offset(x: 2)\n                .opacity(isHoveringScheme || isSchemePopOverPresented ? 0.0 : 1.0)\n        }\n        .background {\n            if isHoveringScheme || isSchemePopOverPresented {\n                HStack {\n                    Spacer()\n                    chevronDown\n                }\n            }\n        }\n        .padding(6)\n        .padding(.leading, 2) // apparently this is cummulative?\n        .background {\n            Color(nsColor: colorScheme == .dark ? .white : .black)\n                .opacity(isHoveringScheme || isSchemePopOverPresented ? 0.05 : 0)\n                .clipShape(Capsule())\n        }\n    }\n\n    @ViewBuilder private var seqouia: some View {\n        label\n            .padding(.trailing, 11.5)\n            .padding(.horizontal, 2.5)\n            .padding(.vertical, 2.5)\n            .background {\n                Color(nsColor: colorScheme == .dark ? .white : .black)\n                    .opacity(isHoveringScheme || isSchemePopOverPresented ? 0.05 : 0)\n                    .clipShape(RoundedRectangle(cornerSize: CGSize(width: 4, height: 4)))\n                HStack {\n                    Spacer()\n                    if isHoveringScheme || isSchemePopOverPresented {\n                        chevronDown\n                            .padding(.trailing, 2)\n                    } else {\n                        chevron\n                            .padding(.trailing, 4)\n                    }\n                }\n            }\n    }\n\n    @ViewBuilder private var label: some View {\n        HStack(spacing: 6) {\n            Image(systemName: \"folder.badge.gearshape\")\n                .imageScale(.medium)\n            Text(workspaceDisplayName)\n                .frame(minWidth: 0)\n        }\n        .opacity(activeState == .inactive ? 0.4 : 1.0)\n        .font(.subheadline)\n    }\n\n    @ViewBuilder private var chevron: some View {\n        Image(systemName: \"chevron.compact.right\")\n            .font(.system(size: 9, weight: .medium, design: .default))\n            .foregroundStyle(.secondary)\n            .scaleEffect(x: 1.30, y: 1.0, anchor: .center)\n            .imageScale(.large)\n    }\n\n    @ViewBuilder private var chevronDown: some View {\n        VStack(spacing: 1) {\n            Image(systemName: \"chevron.down\")\n        }\n        .font(.system(size: 8, weight: .semibold, design: .default))\n        .padding(.top, 0.5)\n    }\n\n    @ViewBuilder var popoverContent: some View {\n        WorkspaceMenuItemView(\n            workspaceFileManager: workspaceFileManager,\n            item: workspaceFileManager?.workspaceItem\n        )\n        Divider()\n            .padding(.vertical, 5)\n        Group {\n            OptionMenuItemView(label: \"Add Folder...\") {\n                // TODO: Implment Add Folder\n                print(\"NOT IMPLEMENTED\")\n            }\n            .disabled(true)\n            OptionMenuItemView(label: \"Workspace Settings...\") {\n                NSApp.sendAction(\n                    #selector(CodeEditWindowController.openWorkspaceSettings(_:)), to: nil, from: nil\n                )\n            }\n        }\n    }\n}\n\n// #Preview {\n//    SchemeDropDownMenuView()\n// }\n"
  },
  {
    "path": "CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift",
    "content": "//\n//  TaskDropDownView.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 24.06.24.\n//\n\nimport SwiftUI\n\nstruct TaskDropDownView: View {\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @Environment(\\.controlActiveState)\n    private var activeState\n\n    @ObservedObject var taskManager: TaskManager\n\n    @State private var isTaskPopOverPresented: Bool = false\n    @State private var isHoveringTasks: Bool = false\n\n    var body: some View {\n        Group {\n            if #available(macOS 26, *) {\n                tahoe\n            } else {\n                seqouia\n            }\n        }\n        .onHover { hovering in\n            self.isHoveringTasks = hovering\n        }\n        .instantPopover(isPresented: $isTaskPopOverPresented, arrowEdge: .top) {\n            taskPopoverContent\n        }\n        .onTapGesture {\n            self.isTaskPopOverPresented.toggle()\n        }\n        .accessibilityElement(children: .combine)\n        .accessibilityAddTraits(.isButton)\n        .accessibilityIdentifier(\"TaskDropdown\")\n        .accessibilityValue(taskManager.selectedTask?.name ?? \"Create Tasks\")\n        .accessibilityLabel(\"Active Task\")\n        .accessibilityHint(\"Open the active task menu\")\n        .accessibilityAction {\n            isTaskPopOverPresented = true\n        }\n    }\n\n    @available(macOS 26, *)\n    @ViewBuilder private var tahoe: some View {\n        HStack(spacing: 4) {\n            label\n            chevronIcon\n                .opacity(isHoveringTasks || isTaskPopOverPresented ? 1.0 : 0.0)\n        }\n        .padding(6)\n        .background {\n            Color(nsColor: colorScheme == .dark ? .white : .black)\n                .opacity(isHoveringTasks || isTaskPopOverPresented ? 0.05 : 0)\n                .clipShape(Capsule())\n        }\n    }\n\n    @ViewBuilder private var seqouia: some View {\n        label\n            .opacity(activeState == .inactive ? 0.4 : 1.0)\n            .padding(.trailing, 11.5)\n            .padding(.horizontal, 2.5)\n            .padding(.vertical, 2.5)\n            .background(backgroundColor)\n    }\n\n    @ViewBuilder private var label: some View {\n        Group {\n            if let selectedTask = taskManager.selectedTask {\n                if let selectedActiveTask = taskManager.activeTasks[selectedTask.id] {\n                    ActiveTaskView(activeTask: selectedActiveTask)\n                        .fixedSize()\n                } else {\n                    TaskView(task: selectedTask, status: CETaskStatus.notRunning)\n                        .fixedSize()\n                }\n            } else {\n                Text(\"Create Tasks\")\n                    .frame(minWidth: 0)\n            }\n        }\n        .font(.subheadline)\n    }\n\n    @ViewBuilder private var backgroundColor: some View {\n        Color(nsColor: colorScheme == .dark ? .white : .black)\n            .opacity(isHoveringTasks || isTaskPopOverPresented ? 0.05 : 0)\n            .clipShape(RoundedRectangle(cornerSize: CGSize(width: 4, height: 4)))\n            .overlay(\n                HStack {\n                    Spacer()\n                    if isHoveringTasks || isTaskPopOverPresented {\n                        chevronIcon\n                    }\n                }\n            )\n    }\n\n    @ViewBuilder private var chevronIcon: some View {\n        Image(systemName: \"chevron.down\")\n            .font(.system(size: 8, weight: .bold, design: .default))\n            .padding(.top, 0.5)\n            .padding(.trailing, 2)\n    }\n\n    @ViewBuilder private var taskPopoverContent: some View {\n        if !taskManager.availableTasks.isEmpty {\n            ForEach(taskManager.availableTasks, id: \\.id) { task in\n                TasksPopoverMenuItem(taskManager: taskManager, task: task) {\n                    isTaskPopOverPresented = false\n                }\n            }\n            Divider()\n                .padding(.vertical, 5)\n        }\n        OptionMenuItemView(label: \"Add Task...\") {\n            NSApp.sendAction(#selector(CodeEditWindowController.openWorkspaceSettings(_:)), to: nil, from: nil)\n        }\n        OptionMenuItemView(label: \"Manage Tasks...\") {\n            NSApp.sendAction(#selector(CodeEditWindowController.openWorkspaceSettings(_:)), to: nil, from: nil)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/ActivityViewer/Tasks/TaskView.swift",
    "content": "//\n//  TaskView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 8/4/24.\n//\n\nimport SwiftUI\n\n/// `TaskView` represents a single active task and observes its state.\n/// - Parameter task: The task to be displayed and observed.\n/// - Parameter status: The status of the task to be displayed.\nstruct TaskView: View {\n    @ObservedObject var task: CETask\n    var status: CETaskStatus\n\n    var body: some View {\n        HStack(spacing: 5) {\n            Image(systemName: \"gearshape\")\n            Text(task.name)\n                .frame(minWidth: 0)\n            Spacer(minLength: 0)\n        }\n        .padding(.trailing, 7.5)\n        .overlay(alignment: .trailing) {\n            Circle()\n                .fill(status.color)\n                .frame(width: 5, height: 5)\n                .padding(.trailing, 2.5)\n        }\n        .accessibilityElement()\n        .accessibilityLabel(task.name)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/ActivityViewer/Tasks/TasksPopoverMenuItem.swift",
    "content": "//\n//  TasksPopoverMenuItem.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 8/4/24.\n//\n\nimport SwiftUI\n\n/// - Note: This view **cannot** use the `dismiss` environment value to dismiss the sheet. It has to negate the boolean\n///         value that presented it initially.\n///         See ``SwiftUI/View/instantPopover(isPresented:arrowEdge:content:)``\nstruct TasksPopoverMenuItem: View {\n    @ObservedObject var taskManager: TaskManager\n    var task: CETask\n    var dismiss: () -> Void\n\n    var body: some View {\n        HStack(spacing: 5) {\n            selectionIndicator\n            popoverContent\n        }\n        .dropdownItemStyle()\n        .onTapGesture(perform: selectAction)\n        .accessibilityElement()\n        .accessibilityLabel(task.name)\n        .accessibilityAction(.default, selectAction)\n        .accessibilityAddTraits(taskManager.selectedTaskID == task.id ? [.isSelected] : [])\n    }\n\n    private var selectionIndicator: some View {\n        Group {\n            if taskManager.selectedTaskID == task.id {\n                Image(systemName: \"checkmark\")\n                    .fontWeight(.bold)\n                    .imageScale(.small)\n                    .frame(width: 10)\n            } else {\n                Spacer()\n                    .frame(width: 10)\n            }\n        }\n    }\n\n    private var popoverContent: some View {\n        Group {\n            if let activeTask = taskManager.activeTasks[task.id] {\n                ActiveTaskView(activeTask: activeTask)\n            } else {\n                TaskView(task: task, status: taskManager.taskStatus(taskID: task.id))\n            }\n        }\n    }\n\n    private func selectAction() {\n        taskManager.selectedTaskID = task.id\n        dismiss()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/ActivityViewer/Tasks/WorkspaceMenuItemView.swift",
    "content": "//\n//  WorkspaceMenuItemView.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 24.06.24.\n//\n\nimport SwiftUI\n\nstruct WorkspaceMenuItemView: View {\n    var workspaceFileManager: CEWorkspaceFileManager?\n    var item: CEWorkspaceFile?\n\n    var body: some View {\n        HStack(spacing: 5) {\n            if workspaceFileManager?.workspaceItem.fileName() == item?.name {\n                Image(systemName: \"checkmark\")\n                    .fontWeight(.bold)\n                    .imageScale(.small)\n                    .frame(width: 10)\n            } else {\n                Spacer()\n                    .frame(width: 10)\n            }\n            Image(systemName: \"folder.badge.gearshape\")\n                .imageScale(.medium)\n            Text(item?.name ?? \"\")\n            Spacer()\n        }\n        .dropdownItemStyle()\n        .onTapGesture { } // add accessibility action when this is filled in\n        .clipShape(RoundedRectangle(cornerRadius: 5))\n        .accessibilityElement()\n        .accessibilityLabel(item?.name ?? \"\")\n    }\n}\n\n#Preview {\n    WorkspaceMenuItemView()\n}\n"
  },
  {
    "path": "CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFile+Recursion.swift",
    "content": "//\n//  CEWorkspaceFile+Recursion.swift\n//  CodeEdit\n//\n//  Created by Matthijs Eikelenboom on 30/04/2023.\n//\n\nimport Foundation\n\nextension CEWorkspaceFile {\n    /// Flattens the children of `self` recursively with depth.\n    /// - Parameters:\n    ///   - depth: An int that indicates the how deep the tree files need to be flattened\n    ///   - ignoringFolders: A boolean on whether to ignore files that are Folders\n    ///   - fileManager: The workspace's file manager to use.\n    /// - Returns: An array of flattened `CEWorkspaceFiles`\n    func flattenedChildren(\n        withDepth depth: Int,\n        ignoringFolders: Bool,\n        using fileManager: CEWorkspaceFileManager\n    ) -> [CEWorkspaceFile] {\n        guard depth > 0 else { return [] }\n        guard self.isFolder else { return [self] }\n        var childItems: [CEWorkspaceFile] = ignoringFolders ? [] : [self]\n        fileManager.childrenOfFile(self)?.forEach { child in\n            childItems.append(contentsOf: child.flattenedChildren(\n                withDepth: depth - 1,\n                ignoringFolders: ignoringFolders,\n                using: fileManager\n            ))\n        }\n        return childItems\n    }\n\n    /// Returns a list of `CEWorkspaceFiles` that are sibilings of `self`.\n    /// The `height` parameter lets the function navigate up the folder hierarchy to\n    /// select a starting point from which it should start flettening the items.\n    /// - Parameters:\n    ///   - height: `Int` that tells where to start in the hierarchy\n    ///   - ignoringFolders: Wether the sibling folders should be flattened\n    ///   - fileManager: The workspace's file manager to use.\n    /// - Returns: A list of `FileSystemItems`\n    func flattenedSiblings(\n        withHeight height: Int,\n        ignoringFolders: Bool,\n        using fileManager: CEWorkspaceFileManager\n    ) -> [CEWorkspaceFile] {\n        let topMostParent = self.getParent(withHeight: height)\n        return topMostParent.flattenedChildren(withDepth: height, ignoringFolders: ignoringFolders, using: fileManager)\n    }\n\n    /// Using the current instance of `FileSystemItem` it will walk back up the Workspace file hiarchy\n    /// the amount of times specified with the `withHeight` parameter.\n    /// - Parameter height: The amount of times you want to up a folder.\n    /// - Returns: The found `FileSystemItem` object, This should always be a folder.\n    private func getParent(withHeight height: Int) -> CEWorkspaceFile {\n        var topmostParent = self\n        for _ in 0..<height {\n            guard let parent = topmostParent.parent else { break }\n            topmostParent = parent\n        }\n\n        return topmostParent\n    }\n\n#if DEBUG\n    /// Print a debug description of the file.\n    /// - Parameters:\n    ///   - tabCount: The number of tabs to tab the description over (for recursive calls)\n    ///   - fileManager: The file manager to use to find children.\n    /// - Returns: A string describing the file and it's children.\n    /// - Authors: Mattijs Eikelenboom, KaiTheRedNinja. *Moved from 7c27b1e*\n    func childrenDescription(tabCount: Int = 0, using fileManager: CEWorkspaceFileManager) -> String {\n        var myDetails = \"\\(String(repeating: \"|  \", count: max(tabCount - 1, 0)))\\(tabCount != 0 ? \"╰--\" : \"\")\"\n        myDetails += \"\\(url.path(percentEncoded: false))\"\n        if !self.isFolder { // if im a file, just return the url\n            return myDetails\n        } else { // if im a folder, return the url and its children's details\n            var childDetails = \"\\(myDetails)\"\n            if fileManager.hasLoadedChildrenFor(file: self) {\n                for child in fileManager.childrenOfFile(self) ?? [] {\n                    childDetails += \"\\n\\(child.childrenDescription(tabCount: tabCount + 1, using: fileManager))\"\n                }\n            } else {\n                // Disabling for debug line.\n                // swiftlint:disable:next line_length\n                childDetails += \"\\n\\(String(repeating: \"|  \", count: max(tabCount - 1, 0)))\\(tabCount != 0 ? \"╰--\" : \"\") Children Not Loaded\"\n            }\n            return childDetails\n        }\n    }\n#endif\n}\n"
  },
  {
    "path": "CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFile.swift",
    "content": "//\n//  FileItem.swift\n//  CodeEdit\n//\n//  Created by Matthijs Eikelenboom on 07/02/2023.\n//\n\nimport Foundation\nimport SwiftUI\nimport UniformTypeIdentifiers\nimport Combine\n\n/// An object containing all necessary information and actions for a specific file in the workspace\n///\n/// The ``CEWorkspaceFile`` represents every type of file that can exist on the file system. Directories, files,\n/// symlinks, etc. This class does not assume anything about what it is representing, but it can be interrogated to find\n/// out what it represents. All information about the file is derived from the `URL` passed to the initializer of the\n/// object.\n///\n/// This object works to provide a consistent API for any component that needs to work with files, and is as small as\n/// possible.\n///\n/// These objects should be fetched from the ``CEWorkspaceFileManager`` whenever possible. Objects fetched from there\n/// will be connected in CodeEdit's file tree, and structural properties like ``CEWorkspaceFile/parent`` will exist.\n/// They can, however, be created standalone when necessary. Creating a standalone ``CEWorkspaceFile`` is useful if\n/// loading all intermediate subdirectories (from the nearest cached parent to the file) has not been done yet and doing\n/// so would be unnecessary.\n///\n/// An example of this is in the ``OpenQuicklyView``. This view finds a file URL via a search bar, and needs to display\n/// a quick preview of the file. There's a good chance the file is deep in some subdirectory of the workspace, so\n/// fetching it from the ``CEWorkspaceFileManager`` may require loading and caching multiple directories. Instead, it\n/// just makes a disconnected object and uses it for the preview. Then, when opening the file in the workspace it\n/// forces the file to be loaded and cached.\nfinal class CEWorkspaceFile: Codable, Comparable, Hashable, Identifiable, EditorTabRepresentable {\n\n    /// The id of the ``CEWorkspaceFile``.\n    var id: String\n\n    /// Returns the file name (e.g.: `Package.swift`)\n    var name: String { url.lastPathComponent.trimmingCharacters(in: .whitespacesAndNewlines) }\n\n    /// Returns the extension of the file or an empty string if no extension is present.\n    var type: FileIcon.FileType {\n        let filename = url.fileName\n\n        /// First, check if there is a valid file extension.\n        if let type = FileIcon.FileType(rawValue: filename) {\n            return type\n        } else {\n            /// If  there's not, verifies every extension for a valid type.\n            let extensions = filename.dropFirst().components(separatedBy: \".\").reversed()\n\n            return extensions\n                .compactMap { FileIcon.FileType(rawValue: $0) }\n                .first\n            /// Returns .txt for invalid type.\n            ?? .txt\n        }\n    }\n\n    /// Returns the URL of the ``CEWorkspaceFile``\n    let url: URL\n\n    /// Returns the resolved symlink url of this object.\n    lazy var resolvedURL: URL = {\n        url.isSymbolicLink ? url.resolvingSymlinksInPath() : url\n    }()\n\n    /// Return the icon of the file as `Image`\n    var icon: Image {\n        if let customImage = NSImage.symbol(named: systemImage) {\n            return Image(nsImage: customImage)\n        } else {\n            return Image(systemName: systemImage)\n        }\n    }\n\n    /// Return the icon of the file as `NSImage`\n    var nsIcon: NSImage {\n        if let customImage = NSImage.symbol(named: systemImage) {\n            return customImage\n        } else {\n            return NSImage(systemSymbolName: systemImage, accessibilityDescription: systemImage)\n                ?? NSImage(systemSymbolName: \"doc\", accessibilityDescription: \"doc\")!\n        }\n    }\n\n    /// Returns a parent ``CEWorkspaceFile``.\n    ///\n    /// If the item already is the top-level ``CEWorkspaceFile`` this returns `nil`.\n    weak var parent: CEWorkspaceFile?\n\n    private let fileDocumentSubject = PassthroughSubject<CodeFileDocument?, Never>()\n\n    weak var fileDocument: CodeFileDocument? {\n        didSet {\n            fileDocumentSubject.send(fileDocument)\n        }\n    }\n\n    /// Publisher for fileDocument property\n    var fileDocumentPublisher: AnyPublisher<CodeFileDocument?, Never> {\n        fileDocumentSubject.eraseToAnyPublisher()\n    }\n\n    var fileIdentifier = UUID().uuidString\n\n    /// Returns the Git status of a file as ``GitStatus``\n    var gitStatus: GitStatus?\n\n    /// Returns a boolean that is true if the file is staged for commit\n    var staged: Bool?\n\n    /// Returns the `id` in ``EditorTabID`` enum form\n    var tabID: EditorTabID { .codeEditor(id) }\n\n    /// Returns a boolean that is true if the resource represented by this object is a directory.\n    lazy var isFolder: Bool = {\n        resolvedURL.isFolder\n    }()\n\n    /// Returns a boolean that is true if the contents of the directory at this path are\n    ///\n    /// Does not indicate if this is a folder, see ``isFolder`` to first check if this object is also a directory.\n    var isEmptyFolder: Bool {\n        (try? CEWorkspaceFile.fileManager.contentsOfDirectory(\n            at: resolvedURL,\n            includingPropertiesForKeys: nil,\n            options: .skipsSubdirectoryDescendants\n        ).isEmpty) ?? true\n    }\n\n    /// Returns a boolean that is true if the file item is the root folder of the workspace.\n    var isRoot: Bool { parent == nil }\n\n    /// Returns a boolean that is true if the file item actually exists in the file system\n    var doesExist: Bool { CEWorkspaceFile.fileManager.fileExists(atPath: self.url.path) }\n\n    /// Returns a string describing a SFSymbol for the current ``CEWorkspaceFile``\n    ///\n    /// Use it like this\n    /// ```swift\n    /// Image(systemName: item.systemImage)\n    /// ```\n    var systemImage: String {\n        if isFolder {\n            // item is a folder\n            return folderIcon()\n        } else {\n            // item is a file\n            return FileIcon.fileIcon(fileType: type)\n        }\n    }\n\n    /// Return the file's UTType\n    var contentType: UTType? {\n        url.contentType\n    }\n\n    /// Returns a `Color` for a specific `fileType`\n    ///\n    /// If not specified otherwise this will return `Color.accentColor`\n    var iconColor: Color {\n        FileIcon.iconColor(fileType: type)\n    }\n\n    init(\n        id: String,\n        url: URL,\n        changeType: GitStatus? = nil,\n        staged: Bool? = false\n    ) {\n        self.id = id\n        self.url = url\n        self.gitStatus = changeType\n        self.staged = staged\n    }\n\n    convenience init(\n        url: URL,\n        changeType: GitStatus? = nil,\n        staged: Bool? = false\n    ) {\n        self.init(\n            id: url.relativePath,\n            url: url,\n            changeType: changeType,\n            staged: staged\n        )\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case id\n        case name\n        case url\n        case changeType\n        case staged\n    }\n\n    required init(from decoder: Decoder) throws {\n        let values = try decoder.container(keyedBy: CodingKeys.self)\n        id = try values.decode(String.self, forKey: .id)\n        url = try values.decode(URL.self, forKey: .url)\n        gitStatus = try values.decode(GitStatus.self, forKey: .changeType)\n        staged = try values.decode(Bool.self, forKey: .staged)\n    }\n\n    func encode(to encoder: Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encode(id, forKey: .id)\n        try container.encode(name, forKey: .name)\n        try container.encode(url, forKey: .url)\n        try container.encode(gitStatus, forKey: .changeType)\n        try container.encode(staged, forKey: .staged)\n    }\n\n    /// Returns a string describing a SFSymbol for folders\n    ///\n    /// If it is the top-level folder this will return `\"square.dashed.inset.filled\"`.\n    /// If it is a `.codeedit` folder this will return `\"folder.fill.badge.gearshape\"`.\n    /// If it has children this will return `\"folder.fill\"` otherwise `\"folder\"`.\n    private func folderIcon() -> String {\n        if self.parent == nil {\n            return \"folder.fill.badge.gearshape\"\n        }\n        if self.name == \".codeedit\" {\n            return \"folder.fill.badge.gearshape\"\n        }\n        return isEmptyFolder ? \"folder\" : \"folder.fill\"\n    }\n\n    /// Returns the file name with optional extension (e.g.: `Package.swift`)\n    func fileName(typeHidden: Bool = false) -> String {\n        typeHidden ? url.deletingPathExtension()\n            .lastPathComponent\n            .trimmingCharacters(in: .whitespacesAndNewlines) : name\n    }\n\n    /// Generates a string based on user's file name preferences.\n    /// - Returns: A `String` suitable for display.\n    func labelFileName() -> String {\n        let prefs = Settings.shared.preferences.general\n        switch prefs.fileExtensionsVisibility {\n        case .hideAll:\n            return self.fileName(typeHidden: true)\n        case .showAll:\n            return self.fileName(typeHidden: false)\n        case .showOnly:\n            return self.fileName(typeHidden: !prefs.shownFileExtensions.extensions.contains(self.type.rawValue))\n        case .hideOnly:\n            return self.fileName(typeHidden: prefs.hiddenFileExtensions.extensions.contains(self.type.rawValue))\n        }\n    }\n\n    func validateFileName(for newName: String) -> Bool {\n        // Name must be: new, nonempty, valid characters, and not exist in the filesystem.\n        guard newName != labelFileName() &&\n                !newName.isEmpty &&\n                newName.isValidFilename &&\n                !FileManager.default.fileExists(\n                    atPath: self.url.deletingLastPathComponent().appending(path: newName).path\n                ) else {\n            return false\n        }\n\n        return true\n    }\n\n    /// Loads the ``fileDocument`` property with a new ``CodeFileDocument`` and registers it with the shared\n    /// ``CodeEditDocumentController``.\n    func loadCodeFile() throws {\n        let codeFile = try CodeFileDocument(contentsOf: resolvedURL, ofType: contentType?.identifier ?? \"\")\n        CodeEditDocumentController.shared.addDocument(codeFile)\n        self.fileDocument = codeFile\n    }\n\n    // MARK: Statics\n    /// The default `FileManager` instance\n    static let fileManager = FileManager.default\n\n    // MARK: Intents\n    /// Allows the user to view the file or folder in the finder application\n    func showInFinder() {\n        NSWorkspace.shared.activateFileViewerSelecting([url])\n    }\n\n    /// Allows the user to launch the file or folder as it would be in finder\n    func openWithExternalEditor() {\n        NSWorkspace.shared.open(url)\n    }\n\n    /// Nearest folder refers to the parent directory if this is a non-folder item, or itself if the item is a folder.\n    var nearestFolder: URL {\n        (self.isFolder ?\n                    self.url :\n                    self.url.deletingLastPathComponent())\n    }\n\n    // MARK: Comparable\n\n    static func == (lhs: CEWorkspaceFile, rhs: CEWorkspaceFile) -> Bool {\n        lhs.id == rhs.id\n    }\n\n    static func < (lhs: CEWorkspaceFile, rhs: CEWorkspaceFile) -> Bool {\n        lhs.url.lastPathComponent < rhs.url.lastPathComponent\n    }\n\n    // MARK: Hashable\n\n    func hash(into hasher: inout Hasher) {\n        hasher.combine(url)\n        hasher.combine(id)\n    }\n\n}\n"
  },
  {
    "path": "CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileIcon.swift",
    "content": "//\n//  FileIcon.swift\n//  \n//\n//  Created by Nanashi Li on 2022/05/20.\n//\n\nimport SwiftUI\n\n// TODO: DOCS (Nanashi Li)\nenum FileIcon {\n\n    // swiftlint:disable identifier_name\n    enum FileType: String {\n        case adb\n        case aif\n        case avi\n        case bash\n        case c\n        case cetheme\n        case clj\n        case cls\n        case cs\n        case css\n        case d\n        case dart\n        case elm\n        case entitlements\n        case env\n        case ex\n        case example\n        case f95\n        case fs\n        case gitignore\n        case go\n        case gs\n        case h\n        case hs\n        case html\n        case ico\n        case java\n        case jl\n        case jpeg\n        case jpg\n        case js\n        case json\n        case jsx\n        case kt\n        case l\n        case LICENSE\n        case lock\n        case lsp\n        case lua\n        case m\n        case Makefile\n        case md\n        case mid\n        case mjs\n        case mk\n        case mod\n        case mov\n        case mp3\n        case mp4\n        case pas\n        case pdf\n        case pl\n        case plist\n        case png\n        case py\n        case resolved\n        case rb\n        case rs\n        case rtf\n        case scm\n        case scpt\n        case sh\n        case ss\n        case strings\n        case sum\n        case svg\n        case swift\n        case ts\n        case tsx\n        case txt = \"text\"\n        case vue\n        case wav\n        case xcconfig\n        case yml\n        case zsh\n    }\n\n    // swiftlint:enable identifier_name\n\n    /// Returns a string describing a SFSymbol for files\n    /// If not specified otherwise this will return `\"doc\"`\n    static func fileIcon(fileType: FileType?) -> String { // swiftlint:disable:this cyclomatic_complexity function_body_length line_length\n        switch fileType {\n        case .json, .yml, .resolved:\n            return \"doc.json\"\n        case .lock:\n            return \"lock.doc\"\n        case .css:\n            return \"curlybraces\"\n        case .js, .mjs:\n            return \"doc.javascript\"\n        case .jsx, .tsx:\n            return \"atom\"\n        case .swift:\n            return \"swift\"\n        case .env, .example:\n            return \"gearshape.fill\"\n        case .gitignore:\n            return \"arrow.triangle.branch\"\n        case .pdf, .png, .jpg, .jpeg, .ico:\n            return \"photo\"\n        case .svg:\n            return \"square.fill.on.circle.fill\"\n        case .entitlements:\n            return \"checkmark.seal\"\n        case .plist:\n            return \"tablecells\"\n        case .md, .txt:\n            return \"doc.plaintext\"\n        case .rtf:\n            return \"doc.richtext\"\n        case .html:\n            return \"chevron.left.forwardslash.chevron.right\"\n        case .LICENSE:\n            return \"key.fill\"\n        case .java:\n            return \"cup.and.saucer\"\n        case .py:\n            return \"doc.python\"\n        case .rb:\n            return \"doc.ruby\"\n        case .strings:\n            return \"text.quote\"\n        case .h:\n            return \"h.square\"\n        case .m:\n            return \"m.square\"\n        case .vue:\n            return \"v.square\"\n        case .go:\n            return \"g.square\"\n        case .sum:\n            return \"s.square\"\n        case .mod:\n            return \"m.square\"\n        case .bash, .sh, .Makefile, .zsh:\n            return \"terminal\"\n        case .rs:\n            return \"r.square\"\n        case .wav, .mp3, .aif, .mid:\n            return \"speaker.wave.2\"\n        case .avi, .mp4, .mov:\n            return \"film\"\n        case .scpt:\n            return \"applescript\"\n        case .xcconfig:\n            return \"gearshape.2\"\n        case .cetheme:\n            return \"paintbrush\"\n        case .adb, .clj, .cls, .cs, .d, .dart, .elm, .ex, .f95, .fs, .gs, .hs,\n             .jl, .kt, .l, .lsp, .lua, .mk, .pas, .pl, .scm, .ss:\n            return \"doc.plaintext\"\n        default:\n            return \"doc\"\n        }\n    }\n\n    /// Returns a `Color` for a specific `fileType`\n    /// If not specified otherwise this will return `Color.accentColor`\n    static func iconColor(fileType: FileType?) -> Color { // swiftlint:disable:this cyclomatic_complexity\n        switch fileType {\n        case .swift, .html:\n            return .orange\n        case .java, .jpg, .png, .svg, .ts:\n            return .blue\n        case .css:\n            return .teal\n        case .js, .mjs, .py, .entitlements, .LICENSE:\n            return Color.amber\n        case .json, .resolved, .rb, .strings, .yml:\n            return Color.scarlet\n        case .jsx, .tsx:\n            return .cyan\n        case .plist, .xcconfig, .sh:\n            return Color.steel\n        case .c, .cetheme:\n            return .purple\n        case .vue:\n            return Color(red: 0.255, green: 0.722, blue: 0.514, opacity: 1.0)\n        case .h:\n            return Color(red: 0.667, green: 0.031, blue: 0.133, opacity: 1.0)\n        case .m:\n            return Color(red: 0.271, green: 0.106, blue: 0.525, opacity: 1.0)\n        case .go:\n            return Color(red: 0.02, green: 0.675, blue: 0.757, opacity: 1.0)\n        case .sum, .mod:\n            return Color(red: 0.925, green: 0.251, blue: 0.478, opacity: 1.0)\n        case .Makefile:\n            return Color(red: 0.937, green: 0.325, blue: 0.314, opacity: 1.0)\n        case .rs:\n            return .orange\n        default:\n            return Color.steel\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+DirectoryEvents.swift",
    "content": "//\n//  CEWorkspaceFileManager+DirectoryEvents.swift\n//  CodeEdit\n//\n//  Created by Axel Martinez on 5/8/24.\n//\n\nimport Foundation\n\n/// This extension handles the file system events triggered by changes in the root folder.\nextension CEWorkspaceFileManager {\n    /// Called by `fsEventStream` when an event occurs.\n    ///\n    /// This method may be called on a background thread, but all work done by this function will be queued on the main\n    /// thread.\n    /// - Parameter events: An array of events that occurred.\n    func fileSystemEventReceived(events: [DirectoryEventStream.Event]) {\n        DispatchQueue.main.async {\n            var files: Set<CEWorkspaceFile> = []\n            for event in events {\n                // Event returns file/folder that was changed, but in tree we need to update it's parent\n                guard let parentUrl = URL(string: event.path, relativeTo: self.folderUrl)?.deletingLastPathComponent(),\n                      let parentFileItem = self.flattenedFileItems[parentUrl.path] else {\n                    continue\n                }\n\n                switch event.eventType {\n                case .changeInDirectory, .itemChangedOwner, .itemModified:\n                    // Can be ignored for now, these I think not related to tree changes\n                    continue\n                case .rootChanged:\n                    // TODO: #1880 - Handle workspace root changing.\n                    continue\n                case .itemCreated, .itemCloned, .itemRemoved, .itemRenamed:\n                    do {\n                        try self.rebuildFiles(fromItem: parentFileItem)\n                    } catch {\n                        // swiftlint:disable:next line_length\n                        self.logger.error(\"Failed to rebuild files for event: \\(event.eventType.rawValue), path: \\(event.path, privacy: .sensitive)\")\n                    }\n                    files.insert(parentFileItem)\n                }\n            }\n            if !files.isEmpty {\n                self.notifyObservers(updatedItems: files)\n            }\n\n            if Settings.shared.preferences.sourceControl.general.sourceControlIsEnabled &&\n                Settings.shared.preferences.sourceControl.general.refreshStatusLocally {\n                self.handleGitEvents(events: events)\n            }\n        }\n    }\n\n    func handleGitEvents(events: [DirectoryEventStream.Event]) {\n        // Changes excluding .git folder\n        let notGitChanges = events.filter({ !$0.path.contains(\".git/\") })\n\n        // .git folder was changed\n        let gitFolderChange = events.first(where: {\n            $0.path == \"\\(self.folderUrl.relativePath)/.git\"\n        })\n\n        // Change made to git index file, staged/unstaged files\n        let gitIndexChange = events.first(where: {\n            $0.path == \"\\(self.folderUrl.relativePath)/.git/index\"\n        })\n\n        // Change made to git stash\n        let gitStashChange = events.first(where: {\n            $0.path == \"\\(self.folderUrl.relativePath)/.git/refs/stash\"\n        })\n\n        // Changes made to git branches\n        let gitBranchChange = events.first(where: {\n            $0.path.contains(\"\\(self.folderUrl.relativePath)/.git/refs/heads\")\n        })\n\n        // Changes made to git HEAD - current branch changed\n        let gitHeadChange = events.first(where: {\n            $0.path.contains(\"\\(self.folderUrl.relativePath)/.git/HEAD\")\n        })\n\n        // Change made to remotes by looking at .git/config\n        let gitConfigChange = events.first(where: {\n            $0.path == \"\\(self.folderUrl.relativePath)/.git/config\"\n        })\n\n        // If changes were made to project OR files were staged, refresh changes\n        if !notGitChanges.isEmpty || gitIndexChange != nil {\n            Task {\n                await self.sourceControlManager?.refreshAllChangedFiles()\n            }\n        }\n\n        // If changes were stashed, refresh stashed entries\n        if gitStashChange != nil {\n            Task {\n                try await self.sourceControlManager?.refreshStashEntries()\n            }\n        }\n\n        // If branches were added or removed, refresh branches\n        if gitBranchChange != nil {\n            Task {\n                await self.sourceControlManager?.refreshBranches()\n            }\n        }\n\n        // If HEAD was changed, refresh the current branch\n        if gitHeadChange != nil {\n            Task {\n                await self.sourceControlManager?.refreshCurrentBranch()\n            }\n        }\n\n        // If git config changed, refresh remotes\n        if gitConfigChange != nil {\n            Task {\n                try await self.sourceControlManager?.refreshRemotes()\n            }\n        }\n\n        // If .git folder was added or removed, check if repository is valid\n        if gitFolderChange != nil {\n            Task {\n                try await self.sourceControlManager?.validate()\n            }\n        }\n    }\n\n    /// Creates or deletes children of the ``CEWorkspaceFile`` so that they are accurate with the file system,\n    /// instead of creating an entirely new ``CEWorkspaceFile``. Can optionally run a deep rebuild.\n    ///\n    /// This method will return immediately if the given file item is not a directory.\n    /// This will also only rebuild *already cached* directories.\n    /// - Parameters:\n    ///   - fileItem: The ``CEWorkspaceFile``  to correct the children of\n    ///   - deep: Set to `true` if this should perform the rebuild recursively.\n    func rebuildFiles(fromItem fileItem: CEWorkspaceFile, deep: Bool = false) throws {\n        // Do not index directories that are not already loaded.\n        guard childrenMap[fileItem.id] != nil else { return }\n\n        // get the actual directory children\n        let directoryContentsUrls = try fileManager.contentsOfDirectory(\n            at: fileItem.resolvedURL,\n            includingPropertiesForKeys: nil\n        )\n\n        // test for deleted children, and remove them from the index\n        // Folders may or may not have slash at the end, this will normalize check\n        let directoryContentsUrlsRelativePaths = directoryContentsUrls.map({ $0.relativePath })\n        for (idx, oldURL) in (childrenMap[fileItem.id] ?? []).map({ URL(filePath: $0) }).enumerated().reversed()\n        where !directoryContentsUrlsRelativePaths.contains(oldURL.relativePath) {\n            flattenedFileItems.removeValue(forKey: oldURL.relativePath)\n            childrenMap[fileItem.id]?.remove(at: idx)\n        }\n\n        // test for new children, and index them\n        for newContent in directoryContentsUrls {\n            // if the child has already been indexed, continue to the next item.\n            guard !ignoredFilesAndFolders.contains(newContent.lastPathComponent) &&\n                    !(childrenMap[fileItem.id]?.contains(newContent.relativePath) ?? true) else { continue }\n\n            if fileManager.fileExists(atPath: newContent.path) {\n                let newFileItem = createChild(newContent, forParent: fileItem)\n                flattenedFileItems[newFileItem.id] = newFileItem\n                childrenMap[fileItem.id]?.append(newFileItem.id)\n            }\n        }\n\n        childrenMap[fileItem.id] = childrenMap[fileItem.id]?\n            .map { URL(filePath: $0) }\n            .sortItems(foldersOnTop: true)\n            .map { $0.relativePath }\n\n        if deep && childrenMap[fileItem.id] != nil {\n            for child in (childrenMap[fileItem.id] ?? []).compactMap({ flattenedFileItems[$0] }) {\n                try rebuildFiles(fromItem: child)\n            }\n        }\n    }\n\n    /// Notify observers that an update occurred in the watched files.\n    func notifyObservers(updatedItems: Set<CEWorkspaceFile>) {\n        observers.allObjects.reversed().forEach { delegate in\n            guard let delegate = delegate as? CEWorkspaceFileManagerObserver else {\n                observers.remove(delegate)\n                return\n            }\n            delegate.fileManagerUpdated(updatedItems: updatedItems)\n        }\n    }\n\n    /// Add an observer for file system events.\n    /// - Parameter observer: The observer to add.\n    func addObserver(_ observer: CEWorkspaceFileManagerObserver) {\n        observers.add(observer as AnyObject)\n    }\n\n    /// Remove an observer for file system events.\n    /// - Parameter observer: The observer to remove.\n    func removeObserver(_ observer: CEWorkspaceFileManagerObserver) {\n        observers.remove(observer as AnyObject)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+Error.swift",
    "content": "//\n//  CEWorkspaceFileManager+Error.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 1/13/25.\n//\n\nimport Foundation\n\nextension CEWorkspaceFileManager {\n    /// Localized errors related to actions in the file manager.\n    /// These errors are suitable for presentation using `NSAlert(error:)`.\n    enum FileManagerError: LocalizedError {\n        case fileNotFound\n        case fileNotIndexed\n        case originFileNotFound\n        case destinationFileExists\n        case invalidFileName\n\n        var errorDescription: String? {\n            switch self {\n            case .fileNotFound:\n                return \"File not found\"\n            case .fileNotIndexed:\n                return \"File not found in CodeEdit\"\n            case .originFileNotFound:\n                return \"Failed to find origin file\"\n            case .destinationFileExists:\n                return \"Destination already exists\"\n            case .invalidFileName:\n                return \"Invalid file name\"\n            }\n        }\n\n        var recoverySuggestion: String? {\n            switch self {\n            case .fileNotIndexed:\n                return \"Reopen the workspace to reindex the file system.\"\n            case .fileNotFound, .originFileNotFound:\n                return \"The file may have moved during the operation, try again.\"\n            case .destinationFileExists:\n                return \"Use a different file name or remove the conflicting file.\"\n            case .invalidFileName:\n                return \"File names must not contain the : character and be less than 256 characters.\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift",
    "content": "//\n//  CEWorkspaceFileManager+FileSystem.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 9/30/23.\n//\n\nimport Foundation\nimport AppKit\n\nextension CEWorkspaceFileManager {\n    /// This function allows creation of folders in the main directory or sub-folders\n    /// - Parameters:\n    ///   - folderName: The name of the new folder\n    ///   - file: The file to add the new folder to.\n    /// - Returns: The ``CEWorkspaceFile`` representing the folder in the file manager's cache.\n    /// - Authors: Mattijs Eikelenboom, KaiTheRedNinja. *Moved from 7c27b1e*\n    func addFolder(folderName: String, toFile file: CEWorkspaceFile) throws -> CEWorkspaceFile {\n        // Check if folder, if it is create folder under self, else create on same level.\n        var folderUrl = (\n            file.isFolder ? file.url.appending(path: folderName)\n            : file.url.deletingLastPathComponent().appending(path: folderName)\n        )\n\n        // If a file/folder with the same name exists, add a number to the end.\n        var fileNumber = 0\n        while fileManager.fileExists(atPath: folderUrl.path) {\n            fileNumber += 1\n            folderUrl = folderUrl.deletingLastPathComponent().appending(path: \"\\(folderName)\\(fileNumber)\")\n        }\n\n        // Create the folder\n        do {\n            try fileManager.createDirectory(\n                at: folderUrl,\n                withIntermediateDirectories: true,\n                attributes: [:]\n            )\n\n            try rebuildFiles(fromItem: file.isFolder ? file : file.parent ?? file)\n            notifyObservers(updatedItems: [file.isFolder ? file : file.parent ?? file])\n\n            guard let newFolder = getFile(folderUrl.path(), createIfNotFound: true) else {\n                throw FileManagerError.fileNotFound\n            }\n            return newFolder\n        } catch {\n            logger.error(\"Failed to create folder: \\(error, privacy: .auto)\")\n            throw error\n        }\n    }\n\n    /// This function allows creating files in the selected folder or project main directory\n    /// - Parameters:\n    ///   - fileName: The name of the new file\n    ///   - file: The file to add the new file to.\n    ///   - useExtension: The file extension to use. Leave `nil` to guess using relevant nearby files.\n    /// - Authors: Mattijs Eikelenboom, KaiTheRedNinja. *Moved from 7c27b1e*\n    /// - Throws: Throws a `CocoaError.fileWriteUnknown` with the file url if creating the file fails, and calls\n    ///           ``rebuildFiles(fromItem:deep:)`` which throws other `FileManager` errors.\n    /// - Returns: The ``CEWorkspaceFile`` representing the new file in the file manager's cache.\n    func addFile(\n        fileName: String,\n        toFile file: CEWorkspaceFile,\n        useExtension: String? = nil,\n        contents: Data? = nil\n    ) throws -> CEWorkspaceFile {\n        // check the folder for other files, and see what the most common file extension is\n        do {\n            var fileExtension: String\n            if fileName.contains(\".\") {\n                // If we already have a file extension in the name, don't add another one\n                fileExtension = \"\"\n            } else {\n                fileExtension = useExtension ?? findCommonFileExtension(for: file)\n\n                // Don't add a . if the extension is empty, but add it if it's missing.\n                if !fileExtension.isEmpty && !fileExtension.starts(with: \".\") {\n                    fileExtension = \".\" + fileExtension\n                }\n            }\n\n            var fileUrl = file.nearestFolder.appending(path: \"\\(fileName)\\(fileExtension)\")\n            // If a file/folder with the same name exists, add a number to the end.\n            var fileNumber = 0\n            while fileManager.fileExists(atPath: fileUrl.path) {\n                fileNumber += 1\n                fileUrl = fileUrl.deletingLastPathComponent()\n                    .appending(path: \"\\(fileName)\\(fileNumber)\\(fileExtension)\")\n            }\n\n            guard fileUrl.fileName.isValidFilename else {\n                throw FileManagerError.invalidFileName\n            }\n\n            // Create the file\n            guard fileManager.createFile(\n                atPath: fileUrl.path,\n                contents: contents,\n                attributes: [FileAttributeKey.creationDate: Date()]\n            ) else {\n                throw CocoaError.error(.fileWriteUnknown, url: fileUrl)\n            }\n\n            try rebuildFiles(fromItem: file.isFolder ? file : file.parent ?? file)\n            notifyObservers(updatedItems: [file.isFolder ? file : file.parent ?? file])\n\n            // Create if not found here because this should be indexed if we're creating it.\n            // It's not often a user makes a file and then doesn't use it.\n            guard let newFile = getFile(fileUrl.path, createIfNotFound: true) else {\n                throw FileManagerError.fileNotIndexed\n            }\n            return newFile\n        } catch {\n            logger.error(\"Failed to add file: \\(error, privacy: .auto)\")\n            throw error\n        }\n    }\n\n    /// Finds a common file extension in the same directory as a file. Defaults to `txt` if no better alternatives\n    /// are found.\n    /// - Parameter file: The file to use to determine a common extension.\n    /// - Returns: The suggested file extension.\n    private func findCommonFileExtension(for file: CEWorkspaceFile) -> String {\n        var fileExtensions: [String: Int] = [\"\": 0]\n\n        for child in (\n            file.isFolder ? file.flattenedSiblings(withHeight: 2, ignoringFolders: true, using: self)\n            : file.parent?.flattenedSiblings(withHeight: 2, ignoringFolders: true, using: self)\n        ) ?? []\n        where !child.isFolder {\n            // if the file extension was present before, add it now\n            let childFileName = child.fileName(typeHidden: false)\n            if let index = childFileName.lastIndex(of: \".\") {\n                let childFileExtension = \".\\(childFileName.suffix(from: index).dropFirst())\"\n                fileExtensions[childFileExtension] = (fileExtensions[childFileExtension] ?? 0) + 1\n            } else {\n                fileExtensions[\"\"] = (fileExtensions[\"\"] ?? 0) + 1\n            }\n        }\n\n        return fileExtensions.max(by: { $0.value < $1.value })?.key ?? \"txt\"\n    }\n\n    /// This function deletes the item or folder from the current project by moving to Trash\n    /// - Parameters:\n    ///   - file: The file or folder to delete\n    /// - Authors: Paul Ebose\n    public func trash(file: CEWorkspaceFile) throws {\n        do {\n            guard fileManager.fileExists(atPath: file.url.path) else {\n                throw FileManagerError.fileNotFound\n            }\n            try fileManager.trashItem(at: file.url, resultingItemURL: nil)\n        } catch {\n            logger.error(\"Failed to trash file: \\(error, privacy: .auto)\")\n            throw error\n        }\n    }\n\n    /// This function deletes the item or folder from the current project by erasing immediately.\n    /// - Parameters:\n    ///   - file: The file to delete\n    ///   - confirmDelete: True to present an alert to confirm the delete.\n    /// - Authors: Mattijs Eikelenboom, KaiTheRedNinja., Paul Ebose *Moved from 7c27b1e*\n    public func delete(file: CEWorkspaceFile, confirmDelete: Bool = true) throws {\n        // This function also has to account for how the\n        // - file system can change outside of the editor\n        let fileName = file.name\n\n        let deleteConfirmation = NSAlert()\n        deleteConfirmation.messageText = \"Do you want to delete “\\(fileName)”?\"\n        deleteConfirmation.informativeText = \"This item will be deleted immediately. You can't undo this action.\"\n        deleteConfirmation.alertStyle = .critical\n        deleteConfirmation.addButton(withTitle: \"Delete\")\n        deleteConfirmation.buttons.last?.hasDestructiveAction = true\n        deleteConfirmation.addButton(withTitle: \"Cancel\")\n        if !confirmDelete || deleteConfirmation.runModal() == .alertFirstButtonReturn { // \"Delete\" button\n            if fileManager.fileExists(atPath: file.url.path) {\n                try deleteFile(at: file.url)\n            }\n        }\n    }\n\n    /// This function deletes multiple files or folders from the current project by erasing immediately.\n    /// - Parameters:\n    ///   - files: The files to delete\n    ///   - confirmDelete: True to present an alert to confirm the delete.\n    public func batchDelete(files: Set<CEWorkspaceFile>, confirmDelete: Bool = true) throws {\n        let deleteConfirmation = NSAlert()\n        deleteConfirmation.messageText = \"Are you sure you want to delete the \\(files.count) selected items?\"\n        // swiftlint:disable:next line_length\n        deleteConfirmation.informativeText = \"\\(files.count) items will be deleted immediately. You cannot undo this action.\"\n        deleteConfirmation.alertStyle = .critical\n        deleteConfirmation.addButton(withTitle: \"Delete\")\n        deleteConfirmation.buttons.last?.hasDestructiveAction = true\n        deleteConfirmation.addButton(withTitle: \"Cancel\")\n        if !confirmDelete || deleteConfirmation.runModal() == .alertFirstButtonReturn {\n            for file in files where fileManager.fileExists(atPath: file.url.path) {\n                try deleteFile(at: file.url)\n            }\n        }\n    }\n\n    /// Delete a file from the file system.\n    /// - Note: Use ``trash(file:)`` if the file should be moved to the trash. This is irreversible.\n    /// - Parameter url: The file URL to delete.\n    private func deleteFile(at url: URL) throws {\n        do {\n            guard fileManager.fileExists(atPath: url.path) else {\n                throw FileManagerError.fileNotFound\n            }\n            try fileManager.removeItem(at: url)\n        } catch {\n            logger.error(\"Failed to delete file: \\(error, privacy: .auto)\")\n            throw error\n        }\n    }\n\n    /// This function duplicates the item or folder\n    /// - Parameter file: The file to duplicate\n    /// - Authors: Mattijs Eikelenboom, KaiTheRedNinja. *Moved from 7c27b1e*\n    public func duplicate(file: CEWorkspaceFile) throws {\n        // If a file/folder with the same name exists, add \"copy\" to the end\n        var fileUrl = file.url\n        while fileManager.fileExists(atPath: fileUrl.path) {\n            let previousName = fileUrl.lastPathComponent\n            let fileExtension = fileUrl.pathExtension.isEmpty ? \"\" : \".\\(fileUrl.pathExtension)\"\n            let fileName = fileExtension.isEmpty ? previousName :\n            previousName.replacingOccurrences(of: fileExtension, with: \"\")\n            fileUrl = fileUrl.deletingLastPathComponent().appending(path: \"\\(fileName) copy\\(fileExtension)\")\n        }\n\n        if fileManager.fileExists(atPath: file.url.path) {\n            do {\n                try fileManager.copyItem(at: file.url, to: fileUrl)\n            } catch {\n                logger.error(\"Failed to duplicate file: \\(error, privacy: .auto)\")\n                throw error\n            }\n        }\n    }\n\n    /// This function moves the item or folder if possible\n    /// - Parameters:\n    ///   - file: The file to move.\n    ///   - newLocation: The destination to move the file to.\n    /// - Authors: Mattijs Eikelenboom, KaiTheRedNinja. *Moved from 7c27b1e*\n    /// - Returns: The new file object, if it has been indexed. The file manager does not index folders that have not\n    ///            been revealed to save memory. This may move a file deeper into the tree than is indexed. In that\n    ///            case, it is correct to return nothing. This is intentionally different than `addFile`.\n    @discardableResult\n    public func move(file: CEWorkspaceFile, to newLocation: URL) throws -> CEWorkspaceFile? {\n        do {\n            guard fileManager.fileExists(atPath: file.url.path(percentEncoded: false)) else {\n                throw FileManagerError.originFileNotFound\n            }\n\n            guard !fileManager.fileExists(atPath: newLocation.path(percentEncoded: false)) else {\n                throw FileManagerError.destinationFileExists\n            }\n\n            try createMissingParentDirectory(for: newLocation.deletingLastPathComponent())\n\n            try fileManager.moveItem(at: file.url, to: newLocation)\n\n            // This function recursively creates missing directories if the file is moved to a directory that does\n            // not exist\n            func createMissingParentDirectory(for url: URL, createSelf: Bool = true) throws {\n                // if the folder's parent folder doesn't exist, create it.\n                if !fileManager.fileExists(atPath: url.deletingLastPathComponent().path) {\n                    try createMissingParentDirectory(for: url.deletingLastPathComponent())\n                }\n                // if the folder doesn't exist and the function was ordered to create it, create it.\n                if createSelf && !fileManager.fileExists(atPath: url.path) {\n                    // Create the folder\n                    try fileManager.createDirectory(\n                        at: url,\n                        withIntermediateDirectories: true,\n                        attributes: [:]\n                    )\n                }\n            }\n\n            if let parent = file.parent {\n                try rebuildFiles(fromItem: parent)\n                notifyObservers(updatedItems: [parent])\n            }\n\n            // If we have the new parent file, let's rebuild that directory too\n            if let newFileParent = getFile(newLocation.deletingLastPathComponent().path) {\n                try rebuildFiles(fromItem: newFileParent)\n                notifyObservers(updatedItems: [newFileParent])\n            }\n\n            return getFile(newLocation.absoluteURL.path)\n        } catch {\n            logger.error(\"Failed to move file: \\(error, privacy: .auto)\")\n            throw error\n        }\n    }\n\n    /// Copy a file's contents to a new location.\n    /// - Parameters:\n    ///   - file: The file to copy.\n    ///   - newLocation: The location to copy to.\n    public func copy(file: CEWorkspaceFile, to newLocation: URL) throws {\n        do {\n            guard file.url != newLocation && !fileManager.fileExists(\n                atPath: newLocation.absoluteURL.path(percentEncoded: false)\n            ) else {\n                throw FileManagerError.originFileNotFound\n            }\n            try fileManager.copyItem(at: file.url, to: newLocation)\n        } catch {\n            logger.error(\"Failed to copy file: \\(error, privacy: .auto)\")\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager.swift",
    "content": "//\n//  FileSystemClient.swift\n//  CodeEdit\n//\n//  Created by Matthijs Eikelenboom on 04/02/2023.\n//\n\nimport Combine\nimport Foundation\nimport AppKit\nimport OSLog\n\nprotocol CEWorkspaceFileManagerObserver: AnyObject {\n    func fileManagerUpdated(updatedItems: Set<CEWorkspaceFile>)\n}\n\n/// This class is used to load, modify, and listen to files on a user's machine.\n///\n/// The workspace file manager provides an API for:\n/// - Navigating and loading file items.\n/// - Moving and modifying files.\n/// - Listening for file system updates and notifying observers.\n///\n/// File caching in CodeEdit is done lazily. the ``CEWorkspaceFileManager`` will only create ``CEWorkspaceFile``s for\n/// from files that are needed for some UI component, and ignores all other files. This is done primarily to prevent\n/// CodeEdit from wasting resources finding and caching a potentially large file tree (eg, a user's home directory).\n///\n/// When the workspace is first loaded, the file manager will only load the contents of the workspace directory. To find\n/// files after this, calls to ``CEWorkspaceFileManager/getFile(_:createIfNotFound:)`` or\n/// ``CEWorkspaceFileManager/childrenOfFile(_:)`` can cause the children for a file to be loaded into CodeEdit's cache\n/// of files.\n///\n/// Moving and modifying files is done via the methods: ``CEWorkspaceFileManager/addFile(fileName:toFile:)``,\n/// ``CEWorkspaceFileManager/addFolder(folderName:toFile:)``, ``CEWorkspaceFileManager/delete(file:)``,\n/// ``CEWorkspaceFileManager/copy(file:to:)``, and ``CEWorkspaceFileManager/duplicate(file:)``.\n///\n/// To listen for updates, the ``CEWorkspaceFileManager`` uses a ``DirectoryEventStream`` to listen to updates for any\n/// files under the ``CEWorkspaceFileManager/folderUrl`` url. Those can be passed on to listeners that conform to the\n/// ``CEWorkspaceFileManagerObserver`` protocol. Use the ``CEWorkspaceFileManager/addObserver(_:)``\n/// and ``CEWorkspaceFileManager/removeObserver(_:)`` to add or remove observers. Observers are kept as weak references.\nfinal class CEWorkspaceFileManager {\n    let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? \"\", category: \"CEWorkspaceFileManager\")\n    private(set) var fileManager: FileManager\n    private(set) var ignoredFilesAndFolders: Set<String>\n\n    var flattenedFileItems: [String: CEWorkspaceFile]\n    /// Maps all directories to it's children's paths.\n    var childrenMap: [String: [String]] = [:]\n    var fsEventStream: DirectoryEventStream?\n    var observers: NSHashTable<AnyObject> = .weakObjects()\n\n    let folderUrl: URL\n    let workspaceItem: CEWorkspaceFile\n    weak var sourceControlManager: SourceControlManager?\n\n    /// Create a file  manager object with a root and a set of files to ignore.\n    /// - Parameters:\n    ///   - folderUrl: The folder to use as the root of the file manager.\n    ///   - ignoredFilesAndFolders: A set of files to ignore. These should not be paths, but rather file names\n    ///                             like `.DS_Store`\n    init(\n        folderUrl: URL,\n        ignoredFilesAndFolders: Set<String>,\n        fileManager: FileManager = FileManager.default,\n        sourceControlManager: SourceControlManager?\n    ) {\n        self.folderUrl = folderUrl\n        self.ignoredFilesAndFolders = ignoredFilesAndFolders\n\n        self.workspaceItem = CEWorkspaceFile(url: folderUrl)\n        self.flattenedFileItems = [workspaceItem.id: workspaceItem]\n        self.sourceControlManager = sourceControlManager\n        self.fileManager = fileManager\n\n        self.loadChildrenForFile(self.workspaceItem)\n\n        fsEventStream = DirectoryEventStream(directory: self.folderUrl.path) { [weak self] events in\n            self?.fileSystemEventReceived(events: events)\n        }\n\n        Task {\n            try await self.sourceControlManager?.validate()\n        }\n    }\n\n    // MARK: - Public API\n\n    /// A function that, given a file's path, returns a `FileItem` if it exists\n    /// within the scope of the `FileSystemClient`. \n    /// - Parameters:\n    ///   - path: The file's relative path.\n    ///   - createIfNotFound: Set to true if the function should index any intermediate directories to find the file,\n    ///                       as well as index the file if it is not already.\n    /// - Returns: The file item corresponding to the file\n    func getFile(\n        _ path: String,\n        createIfNotFound: Bool = false\n    ) -> CEWorkspaceFile? {\n        if let file = flattenedFileItems[path] {\n            return file\n        } else if createIfNotFound {\n            guard let url = URL(string: path, relativeTo: folderUrl) else {\n                return nil\n            }\n\n            // If file is not in the `folderUrl` or subdirectories, exit.\n            guard folderUrl.containsSubPath(url) else {\n                return nil\n            }\n\n            // Drill down towards the file, indexing any directories needed.\n            let pathComponents = url.pathComponents.dropFirst(folderUrl.pathComponents.count)\n            var currentURL = folderUrl\n\n            for component in pathComponents {\n                currentURL.append(component: component)\n\n                if let file = flattenedFileItems[currentURL.relativePath], childrenMap[file.id] == nil {\n                    loadChildrenForFile(file)\n                }\n            }\n\n            if let file = flattenedFileItems[url.relativePath] {\n                return file\n            } else if let parent = getFile(currentURL.deletingLastPathComponent().path) {\n                // This catches the case where each parent dir has been loaded, their children cached, and this is a new\n                // file, so we still need to create it and add it to the cache.\n                let newFileItem = createChild(url, forParent: parent)\n                flattenedFileItems[newFileItem.id] = newFileItem\n                childrenMap[parent.id]?.append(newFileItem.id)\n                return newFileItem\n            }\n        }\n\n        return nil\n    }\n\n    /// Returns all children for the given file.\n    /// - Note: Will find and cache new children if they have not been already, see\n    ///         ``CEWorkspaceFileManager/getFile(_:createIfNotFound:)`` to force a file to be loaded.\n    /// - Parameter file: The file to find children for.\n    /// - Returns: An array of children for the file, or `nil` if the file was not a directory.\n    func childrenOfFile(_ file: CEWorkspaceFile) -> [CEWorkspaceFile]? {\n        if file.isFolder {\n            if childrenMap[file.id] == nil {\n                // Load the children\n                loadChildrenForFile(file)\n            }\n\n            return childrenMap[file.id]?.compactMap { flattenedFileItems[$0] }\n        }\n\n        return nil\n    }\n\n    /// Loads and caches all children for the given file item.\n    ///\n    /// After calling this method, you can expect `childrenMap` to contain some value\n    /// for the file object, even an empty array.\n    ///\n    /// - Parameter file: The file item to load children for.\n    private func loadChildrenForFile(_ file: CEWorkspaceFile) {\n        guard let children = urlsForDirectory(file.resolvedURL) else {\n            return\n        }\n        var addedChildrenUrls: [String] = []\n        for child in children {\n            let newFileItem = createChild(child, forParent: file)\n            flattenedFileItems[newFileItem.id] = newFileItem\n            addedChildrenUrls.append(newFileItem.id)\n        }\n        childrenMap[file.id] = addedChildrenUrls\n        Task {\n            await sourceControlManager?.refreshAllChangedFiles()\n        }\n    }\n\n    /// Creates an ordered array of all files and directories at the given file object.\n    /// - Parameter file: The file to use.\n    /// - Returns: An ordered array of URLs sorted alphabetically with directories first.\n    private func urlsForDirectory(_ url: URL) -> [URL]? {\n        try? fileManager.contentsOfDirectory(\n            at: url,\n            includingPropertiesForKeys: [.isDirectoryKey],\n            options: [.includesDirectoriesPostOrder, .skipsSubdirectoryDescendants]\n        )\n        .compactMap {\n            ignoredFilesAndFolders.contains($0.lastPathComponent) && (try? $0.checkResourceIsReachable()) ?? false\n            ? nil\n            : URL(filePath: $0.path(percentEncoded: false), relativeTo: folderUrl)\n        }\n        .sortItems(foldersOnTop: true)\n    }\n\n    /// Creates a child item for the specified parent item. Th child item's id is based on the\n    /// parent's id to take into account symlinks.\n    /// - Parameter url: The file url of the child element.\n    /// - Parameter file: The parent element.\n    /// - Returns: A child element with an associated parent.\n    func createChild(_ url: URL, forParent file: CEWorkspaceFile) -> CEWorkspaceFile {\n        let relativeURL = URL(filePath: file.id).appending(path: url.lastPathComponent)\n        let childId = relativeURL.relativePath\n        let newFileItem = CEWorkspaceFile(id: childId, url: relativeURL)\n        newFileItem.parent = file\n        return newFileItem\n    }\n\n#if DEBUG\n    /// Determines if the file has had it's children loaded from disk.\n    /// - Parameter file: The file to check.\n    /// - Returns: True if the file's children have been cached.\n    func hasLoadedChildrenFor(file: CEWorkspaceFile) -> Bool {\n        childrenMap[file.id] != nil\n    }\n#endif\n\n    /// Run when the owner of the ``CEWorkspaceFileManager`` doesn't need it anymore.\n    /// This de-inits most functions in the ``CEWorkspaceFileManager``, so that in case it isn't de-init'd it does not\n    /// use up significant amounts of RAM, and clears any file system event watchers.\n    func cleanUp() {\n        fsEventStream?.cancel()\n        flattenedFileItems = [workspaceItem.id: workspaceItem]\n    }\n\n    deinit {\n        fsEventStream?.cancel()\n        observers.removeAllObjects()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CEWorkspace/Models/DirectoryEventStream.swift",
    "content": "//\n//  DirectoryEventStream.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 6/26/23.\n//\n\nimport Foundation\n\nenum FSEvent: String {\n    case changeInDirectory\n    case rootChanged\n    case itemChangedOwner\n    case itemCreated\n    case itemCloned\n    case itemModified\n    case itemRemoved\n    case itemRenamed\n}\n\n/// Creates a stream of events using the File System Events API.\n///\n/// The stream of events is started immediately upon initialization, and will only be stopped when either `cancel`\n/// is called, or the object is deallocated. The stream is also configured to debounce notifications to happen\n/// according to the `debounceDuration` parameter. This directly corresponds with the `latency` parameter in\n/// `FSEventStreamCreate`, which will delay notifications until `latency` has passed at which point it will send all\n/// the notifications built up during that period of time.\n///\n/// Use the `callback` parameter to listen for notifications.\n/// Notifications are automatically filtered to include certain events, but the FS event API doesn't always correctly\n/// flag events so use caution when handling events as they can come frequently.\n///\n/// The `callback` function will be called with all events that happened since the last event notification,\n/// effectively batching all notifications every `debounceDuration`. This callback may not be called on a\n/// predictable dispatch queue.\nclass DirectoryEventStream {\n    typealias EventCallback = ([Event]) -> Void\n\n    private var streamRef: FSEventStreamRef?\n    private var callback: EventCallback\n    private let debounceDuration: TimeInterval\n\n    struct Event {\n        let path: String\n        let eventType: FSEvent\n    }\n\n    /// Initialize the event stream and begin listening for events.\n    /// - Parameters:\n    ///   - directory: The directory to monitor. The listener may receive a ``FSEvent/rootChanged`` event if this\n    ///   directory is modified or moved.\n    ///   - debounceDuration: The duration to delay notifications for to let the FS events API accumulates events.\n    ///                       defaults to 0.1s.\n    ///   - callback: A callback provided that the ``DirectoryEventStream`` will send events to. See\n    ///               ``DirectoryEventStream``'s documentation for detailed information.\n    init(directory: String, debounceDuration: TimeInterval = 0.1, callback: @escaping EventCallback) {\n        self.debounceDuration = debounceDuration\n        self.callback = callback\n        let selfPtr = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())\n\n        var context = FSEventStreamContext(\n            version: 0,\n            info: selfPtr,\n            retain: nil,\n            release: nil,\n            copyDescription: nil\n        )\n        let contextPtr = withUnsafeMutablePointer(to: &context) { ptr in UnsafeMutablePointer(ptr) }\n\n        let cfDirectory = directory as CFString\n        let pathsToWatch = [cfDirectory] as CFArray\n\n        if let ref = FSEventStreamCreate(\n            kCFAllocatorDefault,\n            // swiflint:ignore:next opening_brace\n            { streamRef, clientCallBackInfo, numEvents, eventPaths, eventFlags, eventIds in\n                guard let clientCallBackInfo else { return }\n                Unmanaged<DirectoryEventStream>\n                    .fromOpaque(clientCallBackInfo)\n                    .takeUnretainedValue()\n                    .eventStreamHandler(streamRef, numEvents, eventPaths, eventFlags, eventIds)\n            },\n            contextPtr,\n            pathsToWatch,\n            UInt64(kFSEventStreamEventIdSinceNow),\n            debounceDuration,\n            FSEventStreamCreateFlags(\n                kFSEventStreamCreateFlagUseCFTypes\n                // This will listen for file changes\n                | kFSEventStreamCreateFlagFileEvents\n                // This provides additional information, like fileId,\n                // it is useful when file renamed, because it's firing to separate events with old and new path,\n                // but they can be linked by file id\n                | kFSEventStreamCreateFlagUseExtendedData\n                // Useful for us, always sends after the debounce duration.\n                | kFSEventStreamCreateFlagNoDefer\n            )\n        ) {\n            self.streamRef = ref\n            FSEventStreamSetDispatchQueue(ref, DispatchQueue.global(qos: .default))\n            FSEventStreamStart(ref)\n        }\n    }\n\n    deinit {\n        if let streamRef {\n            FSEventStreamStop(streamRef)\n            FSEventStreamInvalidate(streamRef)\n            FSEventStreamRelease(streamRef)\n        }\n        streamRef = nil\n    }\n\n    /// Cancels the events watcher.\n    /// This class will have to be re-initialized to begin streaming events again.\n    public func cancel() {\n        if let streamRef {\n            FSEventStreamStop(streamRef)\n            FSEventStreamInvalidate(streamRef)\n            FSEventStreamRelease(streamRef)\n        }\n        streamRef = nil\n    }\n\n    /// Handler for the fs event stream.\n    private func eventStreamHandler(\n        _ streamRef: ConstFSEventStreamRef,\n        _ numEvents: Int,\n        _ eventPaths: UnsafeMutableRawPointer,\n        _ eventFlags: UnsafePointer<FSEventStreamEventFlags>,\n        _ eventIds: UnsafePointer<FSEventStreamEventId>\n    ) {\n        guard let eventDictionaries = unsafeBitCast(eventPaths, to: NSArray.self) as? [NSDictionary] else {\n            return\n        }\n\n        var events: [Event] = []\n\n        for (index, dictionary) in eventDictionaries.enumerated() {\n            // Get get file id use dictionary[kFSEventStreamEventExtendedFileIDKey] as? UInt64\n            guard let path = dictionary[kFSEventStreamEventExtendedDataPathKey] as? String\n            else {\n                continue\n            }\n\n            let fsEvents = getEventsFromFlags(eventFlags[index])\n\n            for event in fsEvents {\n                events.append(.init(path: path, eventType: event))\n            }\n        }\n\n        callback(events)\n    }\n\n    /// Parses ``FSEvent`` from the raw flag value.\n    ///\n    /// There can be multiple events in the raw flag value,\n    /// bacause of how OS processes almost simlutaneous actions – thus this functions returns a `Set` of `FSEvent`.\n    ///\n    /// Often returns ``[FSEvent/changeInDirectory]`` as `FSEventStream` returns\n    /// `kFSEventStreamEventFlagNone (0x00000000)` frequently without more information.\n    /// - Parameter raw: The int value received from the FSEventStream\n    /// - Returns: A `Set` of ``FSEvent``'s if at least one valid was found, or `[]` otherwise.\n    private func getEventsFromFlags(_ raw: FSEventStreamEventFlags) -> Set<FSEvent> {\n        var events: Set<FSEvent> = []\n\n        if raw == 0 {\n            events.insert(.changeInDirectory)\n        }\n        if raw & UInt32(kFSEventStreamEventFlagRootChanged) > 0 {\n            events.insert(.rootChanged)\n        }\n        if raw & UInt32(kFSEventStreamEventFlagItemChangeOwner) > 0 {\n            events.insert(.itemChangedOwner)\n        }\n        if raw & UInt32(kFSEventStreamEventFlagItemCreated) > 0 {\n            events.insert(.itemCreated)\n        }\n        if raw & UInt32(kFSEventStreamEventFlagItemCloned) > 0 {\n            events.insert(.itemCloned)\n        }\n        if raw & UInt32(kFSEventStreamEventFlagItemModified) > 0 {\n            events.insert(.itemModified)\n        }\n        if raw & UInt32(kFSEventStreamEventFlagItemRemoved) > 0 {\n            events.insert(.itemRemoved)\n        }\n        if raw & UInt32(kFSEventStreamEventFlagItemRenamed) > 0 {\n            events.insert(.itemRenamed)\n        }\n\n        return events\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CEWorkspaceSettings/Models/CETask.swift",
    "content": "//\n//  CETask.swift\n//  CodeEdit\n//\n//  Created by Axel Martinez on 2/4/24.\n//\n\nimport Foundation\n\n/// CodeEdit task that will be executed by the task manager.\nclass CETask: ObservableObject, Identifiable, Hashable, Codable {\n    @Published var id = UUID()\n    @Published var name: String = \"\"\n    @Published var target: String = \"\"\n    @Published var workingDirectory: String = \"\"\n    @Published var command: String = \"\"\n    @Published var environmentVariables: [EnvironmentVariable]  = []\n\n    init(\n        name: String = \"\",\n        target: String = \"\",\n        workingDirectory: String = \"\",\n        command: String = \"\",\n        environmentVariables: [CETask.EnvironmentVariable] = []\n    ) {\n        self.name = name\n        self.target = target\n        self.workingDirectory = workingDirectory\n        self.command = command\n        self.environmentVariables = environmentVariables\n    }\n\n    init(target: String) {\n        self.target = target\n    }\n\n    var isInvalid: Bool {\n        name.isEmpty ||\n        command.isEmpty\n    }\n\n    /// Ensures that the shell navigates to the correct folder, and then executes the specified command.\n    var fullCommand: String {\n        // Move into the specified folder if needed\n        let changeDirectoryCommand = workingDirectory.isEmpty ? \"\" : \"cd \\(workingDirectory.escapedDirectory()) && \"\n\n        // Construct the full command\n        return \"\\(changeDirectoryCommand)\\(command)\"\n    }\n\n    /// Converts an array of `EnvironmentVariable` to a dictionary.\n    ///\n    /// - Returns: A dictionary with the environment variable keys and values.\n    var environmentVariablesDictionary: [String: String] {\n        return environmentVariables.reduce(into: [String: String]()) { result, environmentVariable in\n            result[environmentVariable.key] = environmentVariable.value\n        }\n    }\n\n    enum CodingKeys: CodingKey {\n        case name\n        case target\n        case workingDirectory\n        case command\n        case environmentVariables\n    }\n\n    struct EnvironmentVariable: Identifiable, Hashable {\n        var id = UUID()\n        var key: String = \"\"\n        var value: String = \"\"\n\n        init() {}\n\n        init(key: String, value: String) {\n            self.key = key\n            self.value = value\n        }\n    }\n\n    required init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        name = try container.decode(String.self, forKey: .name)\n        target = try container.decodeIfPresent(String.self, forKey: .target) ?? \"\"\n        workingDirectory = try container.decodeIfPresent(String.self, forKey: .workingDirectory) ?? \"\"\n        command = try container.decode(String.self, forKey: .command)\n\n        // Decode environment variables from a dictionary-like structure\n        if let envDict = try container.decodeIfPresent([String: String].self, forKey: .environmentVariables) {\n            environmentVariables = envDict.map { EnvironmentVariable(key: $0.key, value: $0.value) }\n        }\n    }\n\n    func encode(to encoder: Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        if !name.isEmpty {\n            try container.encode(name, forKey: .name)\n        }\n        if !target.isEmpty && target != \"My Mac\" {\n            try container.encode(target, forKey: .target)\n        }\n\n        // TODO: Only save if it isn't the workspaces default directory\n        if !workingDirectory.isEmpty {\n            try container.encode(workingDirectory, forKey: .workingDirectory)\n        }\n        if !command.isEmpty {\n            try container.encode(command, forKey: .command)\n        }\n\n        // Encode environment variables as a dictionary-like structure\n        if !environmentVariables.isEmpty {\n            var envDict = [String: String]()\n            for variable in environmentVariables {\n                envDict[variable.key] = variable.value\n            }\n            try container.encode(envDict, forKey: .environmentVariables)\n        }\n    }\n}\n\nextension CETask {\n    static func == (lhs: CETask, rhs: CETask) -> Bool {\n        return lhs.id == rhs.id &&\n        lhs.name == rhs.name &&\n        lhs.target == rhs.target &&\n        lhs.workingDirectory == rhs.workingDirectory &&\n        lhs.command == rhs.command &&\n        lhs.environmentVariables == rhs.environmentVariables\n    }\n\n    func hash(into hasher: inout Hasher) {\n        hasher.combine(id)\n        hasher.combine(name)\n        hasher.combine(target)\n        hasher.combine(workingDirectory)\n        hasher.combine(command)\n        hasher.combine(environmentVariables)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CEWorkspaceSettings/Models/CEWorkspaceSettings.swift",
    "content": "//\n//  CEWorkspaceSettingsManager.swift\n//  CodeEdit\n//\n//  Created by Axel Martinez on 27/3/24.\n//\n\nimport SwiftUI\nimport Combine\n\n/// The CodeEdit workspace settings model.\nfinal class CEWorkspaceSettings: ObservableObject {\n    @Published public var settings: CEWorkspaceSettingsData = .init()\n\n    private var storeTask: AnyCancellable?\n    private let fileManager = FileManager.default\n\n    private(set) var folderURL: URL\n\n    var settingsURL: URL {\n        folderURL.appending(path: \"settings\").appendingPathExtension(\"json\")\n    }\n\n    init(workspaceURL: URL) {\n        folderURL = workspaceURL.appending(path: \".codeedit\", directoryHint: .isDirectory)\n        loadSettings()\n\n        storeTask = $settings\n            .receive(on: DispatchQueue.main)\n            .throttle(for: 2.0, scheduler: RunLoop.main, latest: true)\n            .sink { _ in\n                try? self.savePreferences()\n            }\n    }\n\n    func cleanUp() {\n        storeTask?.cancel()\n        storeTask = nil\n    }\n\n    deinit {\n        cleanUp()\n    }\n\n    /// Load and construct ``CEWorkspaceSettings`` model from `.codeedit/settings.json`\n    private func loadSettings() {\n        guard fileManager.fileExists(atPath: settingsURL.path),\n              let json = try? Data(contentsOf: settingsURL),\n              let prefs = try? JSONDecoder().decode(CEWorkspaceSettingsData.self, from: json)\n        else { return }\n        self.settings = prefs\n    }\n\n    /// Save``CEWorkspaceSettingsManager`` model to `.codeedit/settings.json`\n    func savePreferences() throws {\n        // If the user doesn't have any settings to save, don't save them.\n        guard !settings.isEmpty() else {\n            // Settings is empty, remove the file & directory if it's empty.\n            if fileManager.fileExists(atPath: settingsURL.path()) {\n                try fileManager.removeItem(at: settingsURL)\n\n                if try fileManager.contentsOfDirectory(atPath: folderURL.path()).isEmpty {\n                    try fileManager.removeItem(at: folderURL)\n                }\n            }\n            return\n        }\n\n        if !fileManager.fileExists(atPath: folderURL.path()) {\n            try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true)\n        }\n\n        let data = try JSONEncoder().encode(settings)\n        let json = try JSONSerialization.jsonObject(with: data)\n        let prettyJSON = try JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted])\n        try prettyJSON.write(to: settingsURL, options: .atomic)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CEWorkspaceSettings/Models/CEWorkspaceSettingsData+ProjectSettings.swift",
    "content": "//\n//  ProjectCEWorkspaceSettings.swift\n//  CodeEdit\n//\n//  Created by Axel Martinez on 27/3/24.\n//\n\nimport SwiftUI\n\nclass ProjectSettings: ObservableObject, Codable {\n    var projectName: String = \"\"\n\n    init() {}\n\n    /// Explicit decoder init for setting default values when key is not present in `JSON`\n    required init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.projectName = try container.decodeIfPresent(String.self, forKey: .projectName) ?? \"\"\n    }\n\n    func isEmpty() -> Bool {\n        projectName == \"\"\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CEWorkspaceSettings/Models/CEWorkspaceSettingsData.swift",
    "content": "//\n//  CEWorkspaceSettingsData.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 01.07.24.\n//\n\nimport Foundation\n\n/// The model of the workspace settings for `CodeEdit` that control the behavior of some functionality at the workspace\n/// level like the workspace name or defining tasks.  A `JSON` representation is persisted in the workspace's\n/// `./codeedit/settings.json`. file\nclass CEWorkspaceSettingsData: ObservableObject, Codable {\n    @Published var project: ProjectSettings = .init()\n    @Published var tasks: [CETask] = []\n\n    init() { }\n\n    enum CodingKeys: CodingKey {\n        case project, tasks\n    }\n\n    /// Explicit decoder init for setting default values when key is not present in `JSON`\n    required init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.project = try container.decodeIfPresent(ProjectSettings.self, forKey: .project) ?? .init()\n        self.tasks = try container.decodeIfPresent([CETask].self, forKey: .tasks) ?? []\n    }\n\n    /// Encode the instance into the encoder\n    func encode(to encoder: Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        if !project.isEmpty() {\n            try container.encode(project, forKey: .project)\n        }\n        if !tasks.isEmpty {\n            try container.encode(tasks, forKey: .tasks)\n        }\n    }\n\n    func isEmpty() -> Bool {\n        project.isEmpty() && tasks.isEmpty\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CEWorkspaceSettings/Views/AddCETaskView.swift",
    "content": "//\n//  AddCETaskView.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 01.07.24.\n//\n\nimport SwiftUI\n\nstruct AddCETaskView: View {\n    @Environment(\\.dismiss)\n    var dismiss\n\n    @EnvironmentObject var workspaceSettingsManager: CEWorkspaceSettings\n    @StateObject var newTask: CETask\n\n    init() {\n        self._newTask = StateObject(wrappedValue: CETask(target: \"My Mac\"))\n    }\n    var body: some View {\n        VStack(spacing: 0) {\n            CETaskFormView(task: newTask)\n            Divider()\n            HStack {\n                Button {\n                    dismiss()\n                } label: {\n                    Text(\"Cancel\")\n                        .frame(minWidth: 56)\n                }\n                Spacer()\n                Button {\n                    workspaceSettingsManager.settings.tasks.append(newTask)\n                    try? workspaceSettingsManager.savePreferences()\n                    dismiss()\n                } label: {\n                    Text(\"Save\")\n                        .frame(minWidth: 56)\n                }\n                .buttonStyle(.borderedProminent)\n                .disabled(newTask.isInvalid)\n            }\n            .padding()\n        }\n        .accessibilityIdentifier(\"AddTaskView\")\n    }\n\n}\n\n#Preview {\n    AddCETaskView()\n}\n"
  },
  {
    "path": "CodeEdit/Features/CEWorkspaceSettings/Views/CETaskFormView.swift",
    "content": "//\n//  CETaskFormView.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 01.07.24.\n//\n\nimport SwiftUI\n\nstruct CETaskFormView: View {\n    @EnvironmentObject var workspaceSettingsManager: CEWorkspaceSettings\n    @ObservedObject var task: CETask\n    @State private var selectedEnvID: UUID?\n\n    var body: some View {\n        Form {\n            Section {\n                TextField(text: $task.name) {\n                    Text(\"Name\")\n                }\n                .accessibilityLabel(\"Task Name\")\n                Picker(\"Target\", selection: $task.target) {\n                    Text(\"My Mac\")\n                        .tag(\"My Mac\")\n\n                    Text(\"SSH\")\n                        .tag(\"SSH\")\n\n                    Text(\"Docker\")\n                        .tag(\"Docker\")\n\n                    Text(\"Docker Compose\")\n                        .tag(\"Docker Compose\")\n                }\n                .disabled(true)\n            }\n\n            Section {\n                TextField(text: $task.command) {\n                    Text(\"Task\")\n                }\n                .accessibilityLabel(\"Task Command\")\n                TextField(text: $task.workingDirectory) {\n                    Text(\"Working Directory\")\n                }\n            }\n\n            Section {\n                List(selection: $selectedEnvID) {\n                    ForEach($task.environmentVariables, id: \\.id) { env in\n                        EnvironmentVariableListItem(\n                            environmentVariable: env,\n                            selectedEnvID: $selectedEnvID,\n                            deleteHandler: removeEnv\n                        )\n                    }\n                }\n                .frame(minHeight: 56)\n                .overlay {\n                    if task.environmentVariables.isEmpty {\n                        Text(\"No environment variables\")\n                            .foregroundStyle(Color(.secondaryLabelColor))\n                    }\n                }\n                .actionBar {\n                    Button {\n                          self.task.environmentVariables.append(CETask.EnvironmentVariable())\n                    } label: {\n                        Image(systemName: \"plus\")\n                    }\n                    Divider()\n                    Button {\n                        removeSelectedEnv()\n                    } label: {\n                        Image(systemName: \"minus\")\n                    }\n                    .disabled(selectedEnvID == nil)\n                }\n                .onDeleteCommand {\n                    removeSelectedEnv()\n                }\n            } header: {\n                Text(\"Environment Variables\")\n            }\n        }\n        .formStyle(.grouped)\n    }\n\n    func removeSelectedEnv() {\n        if let selectedItemId = selectedEnvID {\n            removeEnv(id: selectedItemId)\n        }\n    }\n\n    func removeEnv(id: UUID) {\n        self.task.environmentVariables.removeAll(where: {\n            $0.id == id\n        })\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CEWorkspaceSettings/Views/CEWorkspaceSettingsTaskListView.swift",
    "content": "//\n//  CEWorkspaceSettingsTaskListView.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 01.07.24.\n//\n\nimport SwiftUI\n\nstruct CEWorkspaceSettingsTaskListView: View {\n    @EnvironmentObject var workspaceSettingsManager: CEWorkspaceSettings\n    @EnvironmentObject var taskManager: TaskManager\n\n    @ObservedObject var settings: CEWorkspaceSettingsData\n\n    @Binding var selectedTaskID: UUID?\n    @Binding var showAddTaskSheet: Bool\n\n    var body: some View {\n        if settings.tasks.isEmpty {\n            Text(\"No tasks\")\n                .foregroundColor(.secondary)\n                .frame(maxWidth: .infinity, alignment: .center)\n        } else {\n            ForEach(settings.tasks) { task in\n                TaskTile(task: task)\n                    .contentShape(Rectangle())\n                    .onTapGesture {\n                        self.selectedTaskID = task.id\n                        self.showAddTaskSheet = true\n                    }\n                    .contextMenu {\n                        Button {\n                            self.selectedTaskID = task.id\n                            self.showAddTaskSheet = true\n                        } label: {\n                            Text(\"Edit\")\n                        }\n                        Button {\n                            settings.tasks.removeAll { $0.id == task.id }\n                            try? workspaceSettingsManager.savePreferences()\n                            taskManager.deleteTask(taskID: task.id)\n                        } label: {\n                            Text(\"Delete\")\n                        }\n                    }\n            }\n        }\n    }\n\n    // Every task as to be observed individually\n    private struct TaskTile: View {\n        @ObservedObject var task: CETask\n        var body: some View {\n            HStack {\n                Text(task.name)\n                Spacer()\n                Group {\n                    Text(task.command)\n                    Image(systemName: \"chevron.right\")\n                }\n                .font(.system(.body, design: .monospaced))\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CEWorkspaceSettings/Views/CEWorkspaceSettingsView.swift",
    "content": "//\n//  CEWorkspaceSettingsView.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 01.07.24.\n//\n\nimport SwiftUI\n\nstruct CEWorkspaceSettingsView: View {\n    var dismiss: () -> Void\n\n    @EnvironmentObject var workspaceSettingsManager: CEWorkspaceSettings\n    @EnvironmentObject var workspace: WorkspaceDocument\n\n    @State var selectedTaskID: UUID?\n    @State var showAddTaskSheet: Bool = false\n\n    var body: some View {\n        VStack(spacing: 0) {\n            Form {\n                Section {\n                    TextField(\n                        \"Name\",\n                        text: $workspaceSettingsManager.settings.project.projectName\n                    )\n                    .accessibilityLabel(\"Workspace Name\")\n                } header: {\n                    Text(\"Workspace\")\n                        .accessibilityHidden(true)\n                }\n\n                Section {\n                    CEWorkspaceSettingsTaskListView(\n                        settings: workspaceSettingsManager.settings,\n                        selectedTaskID: $selectedTaskID,\n                        showAddTaskSheet: $showAddTaskSheet\n                    )\n                } header: {\n                    Text(\"Tasks\")\n                } footer: {\n                    HStack {\n                        Spacer()\n                        Button {\n                            selectedTaskID = nil\n                            showAddTaskSheet = true\n                        } label: {\n                            Text(\"Add Task...\")\n                        }\n                    }\n                }\n            }\n            .formStyle(.grouped)\n            .scrollContentBackground(.hidden)\n\n            Divider()\n            HStack {\n                Spacer()\n                Button {\n                    dismiss()\n                } label: {\n                    Text(\"Done\")\n                        .frame(minWidth: 56)\n                }\n                .buttonStyle(.borderedProminent)\n            }\n            .padding()\n        }\n        .sheet(isPresented: $showAddTaskSheet) {\n            if let selectedTaskIndex = workspaceSettingsManager.settings.tasks.firstIndex(where: {\n                $0.id == selectedTaskID\n            }) {\n                EditCETaskView(\n                    task: workspaceSettingsManager.settings.tasks[selectedTaskIndex],\n                    selectedTaskIndex: selectedTaskIndex\n                )\n            } else {\n                AddCETaskView()\n            }\n        }\n    }\n}\n\n#Preview {\n    CEWorkspaceSettingsView(dismiss: { print(\"Dismiss\") })\n}\n"
  },
  {
    "path": "CodeEdit/Features/CEWorkspaceSettings/Views/EditCETaskView.swift",
    "content": "//\n//  EditCETaskView.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 01.07.24.\n//\n\nimport SwiftUI\n\nstruct EditCETaskView: View {\n    @Environment(\\.dismiss)\n    var dismiss\n\n    @EnvironmentObject var workspaceSettingsManager: CEWorkspaceSettings\n    @EnvironmentObject var taskManager: TaskManager\n    @ObservedObject var task: CETask\n\n    let selectedTaskIndex: Int\n\n    var body: some View {\n        VStack(spacing: 0) {\n            CETaskFormView(task: task)\n            Divider()\n            HStack {\n                Button(role: .destructive) {\n                    do {\n                        workspaceSettingsManager.settings.tasks.removeAll(where: {\n                            $0.id == task.id\n                        })\n                        try workspaceSettingsManager.savePreferences()\n                        taskManager.deleteTask(taskID: task.id)\n                        self.dismiss()\n                    } catch {\n                        NSAlert(error: error).runModal()\n                    }\n                } label: {\n                    Text(\"Delete\")\n                        .foregroundStyle(.red)\n                        .frame(minWidth: 56)\n                }\n\n                Spacer()\n\n                Button {\n                    do {\n                        try workspaceSettingsManager.savePreferences()\n                        self.dismiss()\n                    } catch {\n                        NSAlert(error: error).runModal()\n                    }\n                } label: {\n                    Text(\"Done\")\n                        .frame(minWidth: 56)\n                }\n                .buttonStyle(.borderedProminent)\n                .disabled(task.isInvalid)\n            }\n            .padding()\n        }\n    }\n}\n\n// #Preview {\n    //    EditCETaskView()\n// }\n"
  },
  {
    "path": "CodeEdit/Features/CEWorkspaceSettings/Views/EnvironmentVariableListItem.swift",
    "content": "//\n//  EnvironmentVariableListItem.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 01.07.24.\n//\n\nimport SwiftUI\n\nstruct EnvironmentVariableListItem: View {\n    @FocusState private var isKeyFocused: Bool\n\n    @Binding var environmentVariable: CETask.EnvironmentVariable\n    @Binding var selectedEnvID: UUID?\n\n    /// State variables added to prevent an exception when deleting the item in the onChange event.\n    @State var key: String\n    @State var value: String\n\n    var delete: (UUID) -> Void\n\n    init(\n        environmentVariable: Binding<CETask.EnvironmentVariable>,\n        selectedEnvID: Binding<UUID?>,\n        deleteHandler: @escaping (UUID) -> Void\n    ) {\n        self.delete = deleteHandler\n\n        self._key = State(wrappedValue: environmentVariable.key.wrappedValue)\n        self._value = State(wrappedValue: environmentVariable.value.wrappedValue)\n        self._environmentVariable = environmentVariable\n        self._selectedEnvID = selectedEnvID\n    }\n    var body: some View {\n        HStack {\n            TextField(\"\", text: $key)\n                .focused($isKeyFocused)\n                .disableAutocorrection(true)\n                .autocorrectionDisabled()\n                .labelsHidden()\n                .frame(width: 120)\n                .onAppear {\n                    if environmentVariable.key.isEmpty {\n                        isKeyFocused = true\n                    }\n                }\n            Divider()\n            TextField(\"\", text: $value)\n                .disableAutocorrection(true)\n                .autocorrectionDisabled()\n                .labelsHidden()\n        }\n        .onChange(of: isKeyFocused) { _, isFocused in\n            if isFocused {\n                if selectedEnvID != environmentVariable.id {\n                    selectedEnvID = environmentVariable.id\n                }\n            } else {\n                if key.isEmpty {\n                    selectedEnvID = nil\n                    delete(environmentVariable.id)\n                }\n            }\n        }\n        .onChange(of: key) { _, newValue in\n            environmentVariable.key = newValue\n        }\n        .onChange(of: value) { _, newValue in\n            environmentVariable.value = newValue\n        }\n    }\n}\n\n// #Preview {\n//    EnvironmentVariableListItem()\n// }\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Styles/IconButtonStyle.swift",
    "content": "//\n//  IconButtonStyle.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/9/23.\n//\n\nimport SwiftUI\n\nstruct IconButtonStyle: ButtonStyle {\n    var isActive: Bool?\n    var font: Font?\n    var size: CGSize?\n\n    init(isActive: Bool? = nil, font: Font? = nil, size: CGFloat? = nil) {\n        self.isActive = isActive\n        self.font = font\n        self.size = size == nil ? nil : CGSize(width: size ?? 0, height: size ?? 0)\n    }\n\n    init(isActive: Bool? = nil, font: Font? = nil, size: CGSize? = nil) {\n        self.isActive = isActive\n        self.font = font\n        self.size = size\n    }\n\n    init(isActive: Bool? = nil, font: Font? = nil) {\n        self.isActive = isActive\n        self.font = font\n        self.size = nil\n    }\n\n    func makeBody(configuration: ButtonStyle.Configuration) -> some View {\n        IconButton(\n            configuration: configuration,\n            isActive: isActive,\n            font: font,\n            size: size\n        )\n    }\n\n    struct IconButton: View {\n        let configuration: ButtonStyle.Configuration\n        var isActive: Bool\n        var font: Font\n        var size: CGSize?\n        @Environment(\\.controlActiveState)\n        private var controlActiveState\n        @Environment(\\.isEnabled)\n        private var isEnabled: Bool\n        @Environment(\\.colorScheme)\n        private var colorScheme\n\n        init(configuration: ButtonStyle.Configuration, isActive: Bool?, font: Font?, size: CGFloat?) {\n            self.configuration = configuration\n            self.isActive = isActive ?? false\n            self.font = font ?? Font.system(size: 14.5, weight: .regular, design: .default)\n            self.size = size == nil ? nil : CGSize(width: size ?? 0, height: size ?? 0)\n        }\n\n        init(configuration: ButtonStyle.Configuration, isActive: Bool?, font: Font?, size: CGSize?) {\n            self.configuration = configuration\n            self.isActive = isActive ?? false\n            self.font = font ?? Font.system(size: 14.5, weight: .regular, design: .default)\n            self.size = size ?? nil\n        }\n\n        init(configuration: ButtonStyle.Configuration, isActive: Bool?, font: Font?) {\n            self.configuration = configuration\n            self.isActive = isActive ?? false\n            self.font = font ?? Font.system(size: 14.5, weight: .regular, design: .default)\n            self.size = nil\n        }\n\n        var body: some View {\n            configuration.label\n                .font(font)\n                .foregroundColor(\n                    isActive\n                    ? Color(.controlAccentColor)\n                    : Color(.secondaryLabelColor)\n                )\n                .frame(width: size?.width, height: size?.height, alignment: .center)\n                .contentShape(Rectangle())\n                .brightness(\n                    configuration.isPressed\n                    ? colorScheme == .dark\n                    ? 0.5\n                    : isActive ? -0.25 : -0.75\n                    : 0\n                )\n                .opacity(controlActiveState == .inactive ? 0.5 : 1)\n                .symbolVariant(isActive ? .fill : .none)\n        }\n    }\n}\n\nextension ButtonStyle where Self == IconButtonStyle {\n    static func icon(\n        isActive: Bool? = false,\n        font: Font? = Font.system(size: 14.5, weight: .regular, design: .default),\n        size: CGFloat? = 24\n    ) -> IconButtonStyle {\n        return IconButtonStyle(isActive: isActive, font: font, size: size)\n    }\n    static func icon(\n        isActive: Bool? = false,\n        font: Font? = Font.system(size: 14.5, weight: .regular, design: .default),\n        size: CGSize? = CGSize(width: 24, height: 24)\n    ) -> IconButtonStyle {\n        return IconButtonStyle(isActive: isActive, font: font, size: size)\n    }\n    static func icon(\n        isActive: Bool? = false,\n        font: Font? = Font.system(size: 14.5, weight: .regular, design: .default)\n    ) -> IconButtonStyle {\n        return IconButtonStyle(isActive: isActive, font: font)\n    }\n    static var icon: IconButtonStyle { .init() }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Styles/IconToggleStyle.swift",
    "content": "//\n//  IconToggleStyle.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/9/23.\n//\n\nimport SwiftUI\n\nstruct IconToggleStyle: ToggleStyle {\n    var font: Font?\n    var size: CGSize?\n\n    @State var isPressing = false\n\n    init(font: Font? = nil, size: CGFloat? = nil) {\n        self.font = font\n        self.size = size == nil ? nil : CGSize(width: size ?? 0, height: size ?? 0)\n    }\n\n    init(font: Font? = nil, size: CGSize? = nil) {\n        self.font = font\n        self.size = size\n    }\n\n    init(font: Font? = nil) {\n        self.font = font\n        self.size = nil\n    }\n\n    func makeBody(configuration: ToggleStyle.Configuration) -> some View {\n        Button(\n            action: { configuration.isOn.toggle() },\n            label: { configuration.label }\n        )\n        .buttonStyle(.icon(isActive: configuration.isOn, font: font, size: size))\n    }\n}\n\nextension ToggleStyle where Self == IconToggleStyle {\n    static func icon(\n        font: Font? = Font.system(size: 14.5, weight: .regular, design: .default),\n        size: CGFloat? = 24\n    ) -> IconToggleStyle {\n        return IconToggleStyle(font: font, size: size)\n    }\n    static func icon(\n        font: Font? = Font.system(size: 14.5, weight: .regular, design: .default),\n        size: CGSize? = CGSize(width: 24, height: 24)\n    ) -> IconToggleStyle {\n        return IconToggleStyle(font: font, size: size)\n    }\n    static func icon(\n        font: Font? = Font.system(size: 14.5, weight: .regular, design: .default)\n    ) -> IconToggleStyle {\n        return IconToggleStyle(font: font)\n    }\n    static var icon: IconToggleStyle { .init() }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Styles/MenuWithButtonStyle.swift",
    "content": "//\n//  MenuWithButtonStyle.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 08.09.24.\n//\n\nimport SwiftUI\n\n/// A menu styled to resemble a bordered button.\nstruct MenuWithButtonStyle<MenuView: View>: View {\n    var systemImage: String\n    var menu: () -> MenuView\n    var body: some View {\n        Menu { menu() } label: {}\n            .background {\n                Button {} label: {\n                    HStack {\n                        Image(systemName: systemImage)\n                        Image(systemName: \"chevron.down\")\n                            .resizable()\n                            .fontWeight(.bold)\n                            .frame(width: 8, height: 4.8)\n                            .padding(.leading, -1.5)\n                            .padding(.trailing, -2)\n                    }.offset(y: 1)\n                }\n            }\n            .menuStyle(.borderlessButton)\n            .menuIndicator(.hidden)\n            .frame(width: 30)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Styles/OverlayButtonStyle.swift",
    "content": "import SwiftUI\n\n/// A button style for overlay buttons (like close, action buttons in notifications)\nstruct OverlayButtonStyle: ButtonStyle {\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    func makeBody(configuration: Configuration) -> some View {\n        configuration.label\n            .font(.system(size: 10))\n            .foregroundColor(.secondary)\n            .frame(width: 20, height: 20, alignment: .center)\n            .background(Color.primary.opacity(configuration.isPressed ? colorScheme == .dark ? 0.10 : 0.05 : 0.00))\n            .background(.regularMaterial)\n            .overlay(\n                RoundedRectangle(cornerRadius: 10)\n                    .stroke(Color(nsColor: .separatorColor), lineWidth: 2)\n            )\n            .cornerRadius(10)\n            .shadow(\n                color: Color(.black.withAlphaComponent(colorScheme == .dark ? 0.2 : 0.1)),\n                radius: 5,\n                x: 0,\n                y: 2\n            )\n    }\n}\n\nextension ButtonStyle where Self == OverlayButtonStyle {\n    /// A button style for overlay buttons\n    static var overlay: OverlayButtonStyle {\n        OverlayButtonStyle()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Views/CEContentUnavailableView.swift",
    "content": "//\n//  CEContentUnavailableView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/17/23.\n//\n\nimport SwiftUI\n\nstruct CEContentUnavailableView<Actions: View>: View {\n    var label: String\n    var description: String?\n    var systemImage: String?\n    var actions: Actions?\n\n    init(\n        _ label: String,\n        description: String? = nil,\n        systemImage: String? = nil,\n        @ViewBuilder actions: () -> Actions? = { EmptyView() }\n    ) {\n        self.label = label\n        self.description = description\n        self.systemImage = systemImage\n        self.actions = actions()\n    }\n\n    var contentUnavailableView: some View {\n        VStack(spacing: 14) {\n            VStack(spacing: 5) {\n                if systemImage != nil {\n                    Image(systemName: systemImage ?? \"questionmark.app.dashed\")\n                        .font(.system(size: 28))\n                        .foregroundStyle(.tertiary)\n                        .padding(.bottom, 8)\n                }\n                Text(label)\n                    .font(.system(size: 16.5, weight: systemImage != nil ? .bold : .regular))\n                if description != nil {\n                    Text(description ?? \"\")\n                        .font(.system(size: 10))\n                        .multilineTextAlignment(.center)\n                }\n            }\n            if let actionsView = actions {\n                HStack { actionsView }\n            }\n        }\n        .foregroundColor(.secondary)\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n        .contentShape(Rectangle())\n        .controlSize(.small)\n    }\n\n    var body: some View {\n        if #available(macOS 14, *) {\n            contentUnavailableView\n                .buttonStyle(.accessoryBarAction)\n        } else {\n            contentUnavailableView\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Views/CEOutlineGroup.swift",
    "content": "//\n//  CEOutlineGroup.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/27/23.\n//\n\nimport SwiftUI\n\n// This view replaces OutlineGroup, which lacks support for controlling the expanded state.\n\nstruct CEOutlineGroup<DataElement, ID, Leaf>: View where DataElement: Identifiable, ID: Hashable, Leaf: View {\n    let root: DataElement\n    var expandedIds: Binding<[ID: Bool]>?\n    @State var expanded: Bool = false\n    let defaultExpanded: Bool?\n    let childrenKeyPath: KeyPath<DataElement, [DataElement]?>\n    let idKeyPath: KeyPath<DataElement, ID>\n    let content: (DataElement) -> Leaf\n\n    public init(\n        _ root: DataElement,\n        id: KeyPath<DataElement, ID>,\n        defaultExpanded: Bool? = false,\n        expandedIds: Binding<[ID: Bool]>? = nil,\n        children: KeyPath<DataElement, [DataElement]?>,\n        @ViewBuilder content: @escaping (DataElement) -> Leaf\n    ) {\n        self.root = root\n        self.expandedIds = expandedIds\n        self.childrenKeyPath = children\n        self.idKeyPath = id\n        self.defaultExpanded = defaultExpanded\n        self.content = content\n        let rootId = root[keyPath: id]\n        _expanded = State(\n            initialValue: expandedIds?.wrappedValue[rootId] ?? defaultExpanded ?? false\n        )\n    }\n\n    var itemView: some View {\n        content(root)\n            .id(root[keyPath: idKeyPath])\n            .tag(root[keyPath: idKeyPath])\n    }\n\n    var body: some View {\n        switch root[keyPath: childrenKeyPath] {\n        case .none:\n            itemView\n        case .some(let children):\n            DisclosureGroup(isExpanded: Binding(\n                get: {\n                    self.expanded\n                },\n                set: { isExpanded in\n                    self.expanded = isExpanded\n                    let id = root[keyPath: idKeyPath]\n                    expandedIds?.wrappedValue[id] = isExpanded\n                }\n            )) {\n                ForEach(children, id: idKeyPath) {\n                    CEOutlineGroup(\n                        $0,\n                        id: idKeyPath,\n                        defaultExpanded: defaultExpanded,\n                        expandedIds: expandedIds,\n                        children: childrenKeyPath,\n                        content: content\n                    )\n                }\n            } label: {\n                itemView\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Views/Divided.swift",
    "content": "//\n//  Divided.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/18/23.\n//\n\nimport SwiftUI\n\nstruct Divided<Content: View>: View {\n    var content: Content\n\n    init(@ViewBuilder content: () -> Content) {\n        self.content = content()\n    }\n\n    var body: some View {\n        _VariadicView.Tree(DividedLayout()) {\n            content\n        }\n    }\n\n    struct DividedLayout: _VariadicView_MultiViewRoot {\n        @ViewBuilder\n        func body(children: _VariadicView.Children) -> some View {\n            let last = children.last?.id\n\n            ForEach(children) { child in\n                child\n\n                if child.id != last {\n                    Divider()\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Views/EffectView.swift",
    "content": "//\n//  EffectView.swift\n//  CodeEditModules/CodeEditUI\n//\n//  Created by Rehatbir Singh on 15/03/2022.\n//\n\nimport SwiftUI\n\n/// A SwiftUI Wrapper for `NSVisualEffectView`\n///\n/// ## Usage\n/// ```swift\n/// EffectView(material: .headerView, blendingMode: .withinWindow)\n/// ```\nstruct EffectView: NSViewRepresentable {\n    private let material: NSVisualEffectView.Material\n    private let blendingMode: NSVisualEffectView.BlendingMode\n    private let emphasized: Bool\n\n    /// Initializes the\n    /// [`NSVisualEffectView`](https://developer.apple.com/documentation/appkit/nsvisualeffectview)\n    /// with a\n    /// [`Material`](https://developer.apple.com/documentation/appkit/nsvisualeffectview/material) and\n    /// [`BlendingMode`](https://developer.apple.com/documentation/appkit/nsvisualeffectview/blendingmode)\n    ///\n    /// By setting the\n    /// [`emphasized`](https://developer.apple.com/documentation/appkit/nsvisualeffectview/1644721-isemphasized)\n    /// flag, the emphasized state of the material will be used if available.\n    ///\n    /// - Parameters:\n    ///   - material: The material to use. Defaults to `.headerView`.\n    ///   - blendingMode: The blending mode to use. Defaults to `.withinWindow`.\n    ///   - emphasized:A Boolean value indicating whether to emphasize the look of the material. Defaults to `false`.\n    init(\n        _ material: NSVisualEffectView.Material = .headerView,\n        blendingMode: NSVisualEffectView.BlendingMode = .withinWindow,\n        emphasized: Bool = false\n    ) {\n        self.material = material\n        self.blendingMode = blendingMode\n        self.emphasized = emphasized\n    }\n\n    func makeNSView(context: Context) -> NSVisualEffectView {\n        let view = NSVisualEffectView()\n        view.material = material\n        view.blendingMode = blendingMode\n        view.isEmphasized = emphasized\n        view.state = .followsWindowActiveState\n        return view\n    }\n\n    func updateNSView(_ nsView: NSVisualEffectView, context: Context) {\n        nsView.material = material\n        nsView.blendingMode = blendingMode\n    }\n\n    /// Returns the system selection style as an ``EffectView`` if the `condition` is met.\n    /// Otherwise it returns `Color.clear`\n    /// \n    /// - Parameter condition: The condition of when to apply the background. Defaults to `true`.\n    /// - Returns: A View\n    @ViewBuilder\n    static func selectionBackground(_ condition: Bool = true) -> some View {\n        if condition {\n            EffectView(.selection, blendingMode: .withinWindow, emphasized: true)\n        } else {\n            Color.clear\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Views/ErrorDescriptionLabel.swift",
    "content": "//\n//  ErrorDescriptionLabel.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 8/14/25.\n//\n\nimport SwiftUI\n\nstruct ErrorDescriptionLabel: View {\n    let error: Error\n\n    var body: some View {\n        VStack(alignment: .leading) {\n            if let error = error as? LocalizedError {\n                if let description = error.errorDescription {\n                    Text(description)\n                }\n\n                if let reason = error.failureReason {\n                    Text(reason)\n                }\n\n                if let recoverySuggestion = error.recoverySuggestion {\n                    Text(recoverySuggestion)\n                }\n            } else {\n                Text(error.localizedDescription)\n            }\n        }\n    }\n}\n\n#Preview {\n    ErrorDescriptionLabel(error: CancellationError())\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Views/FeatureIcon.swift",
    "content": "//\n//  FeatureIcon.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 12/2/24.\n//\n\nimport SwiftUI\nimport CodeEditSymbols\n\nstruct FeatureIcon: View {\n    private let content: IconContent\n    private let color: Color?\n    private let size: CGFloat\n\n    init(\n        symbol: String,\n        color: Color? = nil,\n        size: CGFloat? = nil\n    ) {\n        self.content = .symbol(symbol)\n        self.color = color ?? .accentColor\n        self.size = size ?? 20\n    }\n\n    init(\n        text: String,\n        textColor: Color? = nil,\n        color: Color? = nil,\n        size: CGFloat? = nil\n    ) {\n        self.content = .text(text, textColor: textColor)\n        self.color = color ?? .accentColor\n        self.size = size ?? 20\n    }\n\n    init(\n        image: Image,\n        size: CGFloat? = nil\n    ) {\n        self.content = .image(image)\n        self.color = nil\n        self.size = size ?? 20\n    }\n\n    private func getSafeImage(named: String) -> Image {\n        if NSImage(systemSymbolName: named, accessibilityDescription: nil) != nil {\n            return Image(systemName: named)\n        } else {\n            return Image(symbol: named)\n        }\n    }\n\n    var body: some View {\n        RoundedRectangle(cornerRadius: size / 4, style: .continuous)\n            .fill(background)\n            .overlay {\n                switch content {\n                case let .symbol(name):\n                    getSafeImage(named: name)\n                        .resizable()\n                        .aspectRatio(contentMode: .fit)\n                        .foregroundColor(.white)\n                        .padding(size / 8)\n                case let .text(text, textColor):\n                    Text(text)\n                        .font(.system(size: size * 0.65))\n                        .foregroundColor(textColor ?? .primary)\n                case let .image(image):\n                    image\n                        .resizable()\n                        .aspectRatio(contentMode: .fill)\n                }\n            }\n            .clipShape(RoundedRectangle(cornerRadius: size / 4, style: .continuous))\n            .shadow(\n                color: Color(NSColor.black).opacity(0.25),\n                radius: size / 40,\n                y: size / 40\n            )\n            .frame(width: size, height: size)\n    }\n\n    private var background: AnyShapeStyle {\n        switch content {\n        case .symbol, .text:\n            return AnyShapeStyle((color ?? .accentColor).gradient)\n        case .image:\n            return AnyShapeStyle(.regularMaterial)\n        }\n    }\n}\n\nprivate enum IconContent {\n    case symbol(String)\n    case text(String, textColor: Color?)\n    case image(Image)\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift",
    "content": "//\n//  GlassEffectView.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 9/2/25.\n//\n\nimport SwiftUI\nimport AppKit\n\nstruct GlassEffectView: NSViewRepresentable {\n    var tintColor: NSColor?\n\n    init(tintColor: NSColor? = nil) {\n        self.tintColor = tintColor\n    }\n\n    func makeNSView(context: Context) -> NSView {\n#if compiler(>=6.2)\n        if #available(macOS 26, *) {\n            let view = NSGlassEffectView()\n            view.cornerRadius = 0\n            view.tintColor = tintColor\n            return view\n        }\n#endif\n        return NSView()\n    }\n\n    func updateNSView(_ nsView: NSView, context: Context) {\n#if compiler(>=6.2)\n        if #available(macOS 26, *), let view = nsView as? NSGlassEffectView {\n            view.tintColor = tintColor\n        }\n#endif\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Views/HelpButton.swift",
    "content": "//\n//  HelpButton.swift\n//  CodeEditModules/CodeEditUI\n//\n//  Created by Lukas Pistrol on 30.03.22.\n//\n\nimport SwiftUI\n\n/// A Button representing a system Help button displaying a question mark symbol.\nstruct HelpButton: View {\n\n    private var action: () -> Void\n\n    /// Initializes the ``HelpButton`` with an action closure\n    /// - Parameter action: A closure that gets called once the button is pressed.\n    init(action: @escaping () -> Void) {\n        self.action = action\n    }\n\n    var body: some View {\n        Button(action: action, label: {\n            ZStack {\n                Circle()\n                    .strokeBorder(Color(NSColor.separatorColor), lineWidth: 0.5)\n                    .background(Circle().foregroundColor(Color(NSColor.controlColor)))\n                    .shadow(color: Color(NSColor.separatorColor).opacity(0.3), radius: 0.5)\n                    .shadow(color: Color(NSColor.shadowColor).opacity(0.3), radius: 1, y: 0.5)\n                    .frame(width: 20, height: 20)\n                Image(systemName: \"questionmark\")\n                    .font(.system(size: 12.5, weight: .medium))\n            }\n        })\n        .buttonStyle(PlainButtonStyle())\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Views/InstantPopoverModifier.swift",
    "content": "//\n//  InstantPopoverModifier.swift\n//  CodeEdit\n//\n//  Created by Kihron on 10/26/24.\n//\n\nimport SwiftUI\n\n/// See ``SwiftUI/View/instantPopover(isPresented:arrowEdge:content:)``\n/// - Warning: Views presented using this sheet must be dismissed by negating the `isPresented` binding. Using\n///            SwiftUI's `dismiss` will likely cause a crash. See [FB16221871](rdar://FB16221871)\nstruct InstantPopoverModifier<PopoverContent: View>: ViewModifier {\n    @Binding var isPresented: Bool\n    let arrowEdge: Edge\n    let popoverContent: PopoverContent\n\n    func body(content: Content) -> some View {\n        content\n            .background(\n                PopoverPresenter(\n                    isPresented: $isPresented,\n                    arrowEdge: arrowEdge,\n                    contentView: popoverContent\n                )\n            )\n    }\n}\n\n/// See ``SwiftUI/View/instantPopover(isPresented:arrowEdge:content:)``\n/// - Warning: Views presented using this sheet must be dismissed by negating the `isPresented` binding. Using\n///            SwiftUI's `dismiss` will likely cause a crash. See [FB16221871](rdar://FB16221871)\nstruct PopoverPresenter<ContentView: View>: NSViewRepresentable {\n    @Binding var isPresented: Bool\n    let arrowEdge: Edge\n    let contentView: ContentView\n\n    func makeNSView(context: Context) -> NSView { NSView() }\n\n    func updateNSView(_ nsView: NSView, context: Context) {\n        if isPresented && context.coordinator.popover == nil {\n            let popover = NSPopover()\n            popover.animates = false\n            let hostingController = NSHostingController(rootView: contentView)\n\n            hostingController.view.layoutSubtreeIfNeeded()\n            let contentSize = hostingController.view.fittingSize\n            popover.contentSize = contentSize\n\n            popover.contentViewController = hostingController\n            popover.delegate = context.coordinator\n            popover.behavior = .semitransient\n\n            let nsRectEdge = edgeToNSRectEdge(arrowEdge)\n            popover.show(relativeTo: nsView.bounds, of: nsView, preferredEdge: nsRectEdge)\n            context.coordinator.popover = popover\n\n            if let parentWindow = nsView.window {\n                context.coordinator.startObservingWindow(parentWindow)\n            }\n        } else if !isPresented, let popover = context.coordinator.popover {\n            popover.close()\n            context.coordinator.popover = nil\n        }\n    }\n\n    func makeCoordinator() -> Coordinator {\n        Coordinator(isPresented: $isPresented)\n    }\n\n    class Coordinator: NSObject, NSPopoverDelegate {\n        @Binding var isPresented: Bool\n        var popover: NSPopover?\n\n        init(isPresented: Binding<Bool>) {\n            _isPresented = isPresented\n            super.init()\n        }\n\n        func startObservingWindow(_ window: NSWindow) {\n            /// Observe when the window loses focus\n            NotificationCenter.default.addObserver(\n                forName: NSWindow.didResignKeyNotification,\n                object: window,\n                queue: .main\n            ) { [weak self] _ in\n                guard let self = self else { return }\n                /// The parent window is no longer focused, close the popover\n                DispatchQueue.main.async {\n                    self.isPresented = false\n                    self.popover?.close()\n                }\n            }\n        }\n\n        func popoverWillClose(_ notification: Notification) {\n            DispatchQueue.main.async {\n                self.isPresented = false\n            }\n        }\n\n        func popoverDidClose(_ notification: Notification) {\n            popover = nil\n        }\n    }\n\n    private func edgeToNSRectEdge(_ edge: Edge) -> NSRectEdge {\n        switch edge {\n        case .top: return .minY\n        case .leading: return .minX\n        case .bottom: return .maxY\n        case .trailing: return .maxX\n        }\n    }\n}\n\nextension View {\n    /// A custom view modifier that presents a popover attached to the view with no animation.\n    /// - Warning: Views presented using this sheet must be dismissed by negating the `isPresented` binding. Using\n    ///            SwiftUI's `dismiss` will likely cause a crash. See [FB16221871](rdar://FB16221871)\n    /// - Parameters:\n    ///   - isPresented: A binding to whether the popover is presented.\n    ///   - arrowEdge: The edge of the view that the popover points to. Defaults to `.bottom`.\n    ///   - content: A closure returning the content of the popover.\n    /// - Returns: A view that presents a popover when `isPresented` is `true`.\n    func instantPopover<Content: View>(\n        isPresented: Binding<Bool>,\n        arrowEdge: Edge = .bottom,\n        @ViewBuilder content: @escaping () -> Content\n    ) -> some View {\n        self.modifier(\n            InstantPopoverModifier(\n                isPresented: isPresented,\n                arrowEdge: arrowEdge,\n                popoverContent: PopoverContainer(content: content)\n            )\n        )\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Views/KeyValueTable.swift",
    "content": "//\n//  KeyValueTable.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 5/16/24.\n//\n\nimport SwiftUI\n\nstruct KeyValueItem: Identifiable, Equatable {\n    let id = UUID()\n    let key: String\n    let value: String\n}\n\nprivate struct NewListTableItemView<HeaderView: View>: View {\n    @Environment(\\.dismiss)\n    var dismiss\n\n    @State private var key = \"\"\n    @State private var value = \"\"\n\n    let keyColumnName: String\n    let valueColumnName: String\n    let newItemInstruction: String\n    let validKeys: [String]\n    let headerView: HeaderView?\n    var completion: (String, String) -> Void\n\n    init(\n        key: String? = nil,\n        value: String? = nil,\n        _ keyColumnName: String,\n        _ valueColumnName: String,\n        _ newItemInstruction: String,\n        validKeys: [String],\n        headerView: HeaderView? = nil,\n        completion: @escaping (String, String) -> Void\n    ) {\n        self.key = key ?? \"\"\n        self.value = value ?? \"\"\n        self.keyColumnName = keyColumnName\n        self.valueColumnName = valueColumnName\n        self.newItemInstruction = newItemInstruction\n        self.validKeys = validKeys\n        self.headerView = headerView\n        self.completion = completion\n    }\n\n    var body: some View {\n        VStack(spacing: 0) {\n            Form {\n                Section {\n                    if validKeys.isEmpty {\n                        TextField(keyColumnName, text: $key)\n                            .textFieldStyle(.plain)\n                    } else {\n                        Picker(keyColumnName, selection: $key) {\n                            ForEach(validKeys, id: \\.self) { key in\n                                Text(key).tag(key)\n                            }\n                            Divider()\n                            Text(\"No Selection\").tag(\"\")\n                        }\n                    }\n                    TextField(valueColumnName, text: $value)\n                        .textFieldStyle(.plain)\n                } header: {\n                    if HeaderView.self == EmptyView.self {\n                        Text(newItemInstruction)\n                    } else {\n                        headerView\n                    }\n                }\n            }\n            .formStyle(.grouped)\n            .scrollDisabled(true)\n            .scrollContentBackground(.hidden)\n            .onSubmit {\n                if !key.isEmpty && !value.isEmpty {\n                    completion(key, value)\n                }\n            }\n\n            HStack {\n                Spacer()\n                Button(\"Cancel\") {\n                    dismiss()\n                }\n                Button(\"Add\") {\n                    if !key.isEmpty && !value.isEmpty {\n                        completion(key, value)\n                    }\n                }\n                .buttonStyle(.borderedProminent)\n                .disabled(key.isEmpty || value.isEmpty)\n            }\n            .padding(.horizontal, 20)\n            .padding(.bottom, 20)\n        }\n        .frame(maxWidth: 500)\n    }\n}\n\nstruct KeyValueTable<Header: View, ActionBarView: View>: View {\n    @Binding var items: [String: String]\n\n    let validKeys: [String]\n    let keyColumnName: String\n    let valueColumnName: String\n    let newItemInstruction: String\n    let newItemHeader: () -> Header\n    let actionBarTrailing: () -> ActionBarView\n\n    @State private var editingItem: KeyValueItem?\n    @State private var selection: Set<UUID> = []\n    @State private var tableItems: [KeyValueItem] = []\n\n    init(\n        items: Binding<[String: String]>,\n        validKeys: [String] = [],\n        keyColumnName: String,\n        valueColumnName: String,\n        newItemInstruction: String,\n        @ViewBuilder newItemHeader: @escaping () -> Header = { EmptyView() },\n        @ViewBuilder actionBarTrailing: @escaping () -> ActionBarView = { EmptyView() }\n    ) {\n        self._items = items\n        self.validKeys = validKeys\n        self.keyColumnName = keyColumnName\n        self.valueColumnName = valueColumnName\n        self.newItemInstruction = newItemInstruction\n        self.newItemHeader = newItemHeader\n        self.actionBarTrailing = actionBarTrailing\n    }\n\n    var body: some View {\n        Table(tableItems, selection: $selection) {\n            TableColumn(keyColumnName) { item in\n                Text(item.key)\n            }\n            TableColumn(valueColumnName) { item in\n                Text(item.value)\n            }\n        }\n        .contextMenu(\n            forSelectionType: UUID.self,\n            menu: { selectedItems in\n                Button(\"Edit\") {\n                    editItem(id: selectedItems.first)\n                }\n                Button(\"Remove\") {\n                    removeItem(selectedItems)\n                }\n            },\n            primaryAction: { selectedItems in\n                editItem(id: selectedItems.first)\n            }\n        )\n        .actionBar {\n            HStack(spacing: 2) {\n                Button {\n                    editingItem = KeyValueItem(key: \"\", value: \"\")\n                } label: {\n                    Image(systemName: \"plus\")\n                }\n\n                Divider()\n                    .frame(minHeight: 15)\n\n                Button {\n                    removeItem()\n                } label: {\n                    Image(systemName: \"minus\")\n                }\n                .disabled(selection.isEmpty)\n                .opacity(selection.isEmpty ? 0.5 : 1)\n\n                Spacer()\n\n                actionBarTrailing()\n            }\n        }\n        .sheet(item: $editingItem) { item in\n            NewListTableItemView(\n                key: item.key,\n                value: item.value,\n                keyColumnName,\n                valueColumnName,\n                newItemInstruction,\n                validKeys: validKeys,\n                headerView: newItemHeader()\n            ) { key, value in\n                items[key] = value\n                editingItem = nil\n            }\n        }\n        .cornerRadius(6)\n        .onAppear {\n            updateTableItems(items)\n            if let first = tableItems.first?.id {\n                selection = [first]\n            }\n            selection = []\n        }\n        .onChange(of: items) { _, newValue in\n            updateTableItems(newValue)\n        }\n    }\n\n    private func updateTableItems(_ newValue: [String: String]) {\n        tableItems = items\n            .sorted { $0.key < $1.key }\n            .map { KeyValueItem(key: $0.key, value: $0.value) }\n    }\n\n    private func removeItem() {\n        removeItem(selection)\n        self.selection.removeAll()\n    }\n\n    private func removeItem(_ selection: Set<UUID>) {\n        for selectedId in selection {\n            if let selectedItem = tableItems.first(where: { $0.id == selectedId }) {\n                items.removeValue(forKey: selectedItem.key)\n            }\n        }\n    }\n\n    private func editItem(id: UUID?) {\n        guard let id, let item = tableItems.first(where: { $0.id == id }) else {\n            return\n        }\n        editingItem = item\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Views/PaneTextField.swift",
    "content": "//\n//  PaneTextField.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/2/23.\n//\n\nimport SwiftUI\nimport Combine\n\nstruct PaneTextField<LeadingAccessories: View, TrailingAccessories: View>: View {\n    @Environment(\\.colorScheme)\n    var colorScheme\n\n    @Environment(\\.controlActiveState)\n    private var controlActive\n\n    @FocusState private var isFocused: Bool\n\n    var label: String\n\n    @Binding private var text: String\n\n    let axis: Axis\n\n    let leadingAccessories: LeadingAccessories?\n\n    let trailingAccessories: TrailingAccessories?\n\n    var clearable: Bool\n\n    var onClear: (() -> Void)\n\n    var hasValue: Bool\n\n    init(\n        _ label: String,\n        text: Binding<String>,\n        axis: Axis? = .horizontal,\n        @ViewBuilder leadingAccessories: () -> LeadingAccessories? = { EmptyView() },\n        @ViewBuilder trailingAccessories: () -> TrailingAccessories? = { EmptyView() },\n        clearable: Bool? = false,\n        onClear: (() -> Void)? = {},\n        hasValue: Bool? = false\n    ) {\n        self.label = label\n        _text = text\n        self.axis = axis ?? .horizontal\n        self.leadingAccessories = leadingAccessories()\n        self.trailingAccessories = trailingAccessories()\n        self.clearable = clearable ?? false\n        self.onClear = onClear ?? {}\n        self.hasValue = hasValue ?? false\n    }\n\n    @ViewBuilder\n    public func selectionBackground(\n        _ isFocused: Bool = false\n    ) -> some View {\n        if self.controlActive != .inactive || !text.isEmpty || hasValue {\n            if isFocused || !text.isEmpty || hasValue {\n                Color(.textBackgroundColor)\n            } else {\n                if colorScheme == .light {\n                    Color.black.opacity(0.06)\n                } else {\n                    Color.white.opacity(0.24)\n                }\n            }\n        } else {\n            if colorScheme == .light {\n                Color.clear\n            } else {\n                Color.white.opacity(0.14)\n            }\n        }\n    }\n\n    var body: some View {\n        HStack(alignment: .top, spacing: 0) {\n            if let leading = leadingAccessories {\n                leading\n                    .frame(height: 20)\n            }\n            VStack {\n                TextField(label, text: $text, axis: axis)\n                    .textFieldStyle(.plain)\n                    .focused($isFocused)\n                    .controlSize(.small)\n                    .padding(.horizontal, 8)\n                    .padding(.vertical, 3.5)\n                    .foregroundStyle(.primary)\n            }\n            if clearable == true {\n                Button {\n                    self.text = \"\"\n                    onClear()\n                } label: {\n                    Image(systemName: \"xmark.circle.fill\")\n                }\n                .buttonStyle(.icon(font: .system(size: 11, weight: .semibold), size: CGSize(width: 20, height: 20)))\n                .opacity(text.isEmpty ? 0 : 1)\n                .disabled(text.isEmpty)\n            }\n            if let trailing = trailingAccessories {\n                trailing\n            }\n        }\n        .fixedSize(horizontal: false, vertical: true)\n        .buttonStyle(.icon(font: .system(size: 11, weight: .semibold), size: CGSize(width: 28, height: 20)))\n        .toggleStyle(.icon(font: .system(size: 11, weight: .semibold), size: CGSize(width: 28, height: 20)))\n        .frame(minHeight: 22)\n        .background(\n            selectionBackground(isFocused)\n                .clipShape(RoundedRectangle(cornerRadius: 6))\n                .edgesIgnoringSafeArea(.all)\n        )\n        .overlay(\n            RoundedRectangle(cornerRadius: 6)\n                .stroke(isFocused || !text.isEmpty || hasValue ? .tertiary : .quaternary, lineWidth: 1.25)\n                .clipShape(RoundedRectangle(cornerRadius: 6))\n                .disabled(true)\n                .edgesIgnoringSafeArea(.all)\n        )\n\n        .onTapGesture {\n            isFocused = true\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Views/PanelDivider.swift",
    "content": "//\n//  PanelDivider.swift\n//  \n//\n//  Created by Austin Condiff on 5/10/22.\n//\n\nimport SwiftUI\n\nstruct PanelDivider: View {\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    var body: some View {\n        Divider()\n            .opacity(0)\n            .overlay(\n                Color(.black)\n                    .opacity(colorScheme == .dark ? 0.65 : 0.13)\n            )\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Views/PopoverContainer.swift",
    "content": "//\n//  PopoverContainer.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 8/29/25.\n//\n\nimport SwiftUI\n\n/// Container for SwiftUI views presented in a popover.\n/// On tahoe and above, adds the correct container shape.\nstruct PopoverContainer<ContentView: View>: View {\n    let content: () -> ContentView\n\n    init(@ViewBuilder content: @escaping () -> ContentView) {\n        self.content = content\n    }\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 0) {\n            content()\n        }\n        .font(.subheadline)\n        .if(.tahoe) {\n            $0.padding(13).containerShape(RoundedRectangle(cornerRadius: 20, style: .continuous))\n        } else: {\n            $0.padding(5)\n        }\n        .frame(minWidth: 215)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Views/PressActionsModifier.swift",
    "content": "//\n//  PressActionsModifier.swift\n//  CodeEditModules/CodeEditUI\n//\n//  Created by Gabriel Theodoropoulos on 1/11/20.\n//\n\nimport SwiftUI\n\nstruct PressActions: ViewModifier {\n    var onPress: () -> Void\n    var onRelease: (() -> Void)?\n\n    init(onPress: @escaping () -> Void, onRelease: (() -> Void)? = nil) {\n        self.onPress = onPress\n        self.onRelease = onRelease\n    }\n\n    func body(content: Content) -> some View {\n        content\n            .simultaneousGesture(\n                DragGesture(minimumDistance: 0)\n                    .onChanged({ _ in onPress() })\n                    .onEnded({ _ in onRelease?() })\n            )\n    }\n}\n\nextension View {\n\n    /// A custom view modifier for press actions with callbacks for `onPress` and `onRelease`.\n    /// - Parameters:\n    ///   - onPress: Action to perform once the view is pressed.\n    ///   - onRelease: Action to perform once the view press is released.\n    /// - Returns: some View\n    func pressAction(onPress: @escaping (() -> Void), onRelease: (() -> Void)? = nil) -> some View {\n        modifier(PressActions(onPress: onPress, onRelease: onRelease))\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Views/ScrollOffsetPreferenceKey.swift",
    "content": "import SwiftUI\n\n/// Tracks scroll offset in scrollable views\nstruct ScrollOffsetPreferenceKey: PreferenceKey {\n    typealias Value = CGFloat\n    static var defaultValue = CGFloat.zero\n    static func reduce(value: inout Value, nextValue: () -> Value) {\n        value += nextValue()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Views/SearchField.swift",
    "content": "//\n//  SearchField.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 9/3/24.\n//\n\nimport SwiftUI\n\nstruct SearchField: NSViewRepresentable {\n    @Binding var text: String\n    var placeholder: String\n\n    init(_ placeholder: String, text: Binding<String>) {\n        self.placeholder = placeholder\n        self._text = text\n    }\n\n    func makeNSView(context: Context) -> NSSearchField {\n        let searchField = NSSearchField()\n        searchField.delegate = context.coordinator\n        searchField.placeholderString = placeholder\n        return searchField\n    }\n\n    func updateNSView(_ nsView: NSSearchField, context: Context) {\n        nsView.stringValue = text\n    }\n\n    func makeCoordinator() -> Coordinator {\n        Coordinator(self)\n    }\n\n    class Coordinator: NSObject, NSSearchFieldDelegate {\n        var parent: SearchField\n\n        init(_ parent: SearchField) {\n            self.parent = parent\n        }\n\n        func controlTextDidChange(_ obj: Notification) {\n            if let searchField = obj.object as? NSSearchField {\n                parent.text = searchField.stringValue\n            }\n        }\n    }\n}\n\n#Preview {\n    SearchField(\"Search\", text: .constant(\"Test\"))\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Views/SearchPanel.swift",
    "content": "//\n//  SearchPanel.swift\n//  CodeEditModules/CodeEditUI\n//\n//  Created by Pavel Kasila on 20.03.22.\n//\n\nimport Cocoa\n\nfinal class SearchPanel: NSPanel, NSWindowDelegate {\n    init() {\n        super.init(\n            contentRect: NSRect(x: 0, y: 0, width: 500, height: 48),\n            styleMask: [.fullSizeContentView, .titled, .resizable],\n            backing: .buffered, defer: false\n        )\n        self.delegate = self\n        self.center()\n        self.titlebarAppearsTransparent = true\n        self.isMovableByWindowBackground = true\n    }\n\n    override func standardWindowButton(_ button: NSWindow.ButtonType) -> NSButton? {\n        let button = super.standardWindowButton(button)\n        button?.isHidden = true\n        return button\n    }\n\n    func windowDidResignKey(_ notification: Notification) {\n        if let panel = notification.object as? SearchPanel {\n            panel.close()\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Views/SearchPanelView.swift",
    "content": "//\n//  SearchPanelView.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 3/17/23.\n//\n\nimport Foundation\nimport SwiftUI\n\nstruct SearchPanelView<RowView: View, PreviewView: View, Option: Identifiable & Hashable>: View {\n    @ViewBuilder let rowViewBuilder: ((Option) -> RowView)\n    @ViewBuilder let previewViewBuilder: ((Option) -> PreviewView)?\n\n    @Binding var options: [Option]\n    @Binding var text: String\n\n    @State var selection: Option?\n    @State var previewVisible: Bool = true\n\n    let title: String\n    let image: Image\n    let hasPreview: Bool\n    let onRowClick: ((Option) -> Void)\n    let onClose: (() -> Void)\n    let alwaysShowOptions: Bool\n    let optionRowHeight: CGFloat\n\n    init(\n        title: String,\n        image: Image,\n        options: Binding<[Option]>,\n        text: Binding<String>,\n        alwaysShowOptions: Bool = false,\n        optionRowHeight: CGFloat = 30,\n        content: @escaping ((Option) -> RowView),\n        preview: ((Option) -> PreviewView)? = nil,\n        onRowClick: @escaping ((Option) -> Void),\n        onClose: @escaping () -> Void\n    ) {\n        self.title = title\n        self.image = image\n        self._options = options\n        self._text = text\n        self.rowViewBuilder = content\n        self.previewViewBuilder = preview\n        self.onRowClick = onRowClick\n        self.onClose = onClose\n        self.hasPreview = preview != nil\n        self.alwaysShowOptions = alwaysShowOptions\n        self.optionRowHeight = optionRowHeight\n    }\n\n    var body: some View {\n        VStack(spacing: 0) {\n            VStack {\n                HStack(alignment: .center, spacing: 0) {\n                    image\n                        .font(.system(size: 18))\n                        .foregroundColor(.secondary)\n                        .padding(.leading, 1)\n                        .padding(.trailing, 10)\n                    TextField(title, text: $text)\n                        .font(.system(size: 20, weight: .light, design: .default))\n                        .textFieldStyle(.plain)\n                        .onSubmit {\n                            if let selection {\n                                onRowClick(selection)\n                            } else {\n                                NSSound.beep()\n                            }\n                        }\n                        .task(id: options) {\n                            if options.isEmpty {\n                                selection = nil\n                            } else {\n                                if !options.isEmpty {\n                                    selection = options.first\n                                }\n                            }\n                        }\n                    if hasPreview {\n                        PreviewToggle(previewVisible: $previewVisible)\n                            .onTapGesture {\n                                withAnimation {\n                                    previewVisible.toggle()\n                                }\n                            }\n                    }\n                }\n                .padding(.vertical, 12)\n                .padding(.horizontal, 12)\n                .foregroundColor(.primary.opacity(0.85))\n                .background(EffectView(.sidebar, blendingMode: .behindWindow))\n            }\n            if !text.isEmpty || alwaysShowOptions == true {\n                Divider()\n                    .padding(0)\n                HStack(spacing: 0) {\n                    if options.isEmpty {\n                        Text(\"No matching options\")\n                            .font(.system(size: 17))\n                            .foregroundColor(.secondary)\n                            .frame(maxWidth: hasPreview ? 272 : .infinity, maxHeight: .infinity)\n                    } else {\n                        NSTableViewWrapper(\n                            data: options,\n                            rowHeight: optionRowHeight,\n                            selection: $selection,\n                            itemView: rowViewBuilder\n                        )\n                        .frame(maxWidth: hasPreview && previewVisible ? 272 : .infinity)\n                    }\n                    if hasPreview && previewVisible {\n                        Divider()\n                        if options.isEmpty {\n                            Spacer()\n                                .frame(maxWidth: .infinity)\n                        } else {\n                            if let selection, let previewViewBuilder {\n                                previewViewBuilder(selection)\n                                    .clipped()\n                                    .frame(maxWidth: .infinity)\n                                    .transition(.move(edge: .trailing))\n                            } else {\n                                Text(\"Select an option to preview\")\n                                    .frame(maxWidth: .infinity)\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        .overlay {\n            keyHandlers\n        }\n        .background(EffectView(.sidebar, blendingMode: .behindWindow))\n        .edgesIgnoringSafeArea(.vertical)\n        .frame(\n            minWidth: 680,\n            minHeight: text.isEmpty && !alwaysShowOptions ? 19 : 400,\n            maxHeight: text.isEmpty && !alwaysShowOptions ? 19 : .infinity\n        )\n    }\n\n    @ViewBuilder var keyHandlers: some View {\n        Button {\n            onClose()\n        } label: { EmptyView() }\n            .opacity(0)\n            .keyboardShortcut(.escape, modifiers: [])\n            .accessibilityLabel(\"Close Overlay\")\n        Button {\n            guard selection != options.first else {\n                return\n            }\n            if let selection, let index = options.firstIndex(of: selection) {\n                self.selection = options[index-1]\n            } else {\n                selection = options.first\n            }\n        } label: { EmptyView() }\n            .opacity(0)\n            .keyboardShortcut(.upArrow, modifiers: [])\n            .accessibilityLabel(\"Select Up\")\n        Button {\n            guard selection != options.last else {\n                return\n            }\n            if let selection, let index = options.firstIndex(of: selection) {\n\n                self.selection = options[index+1]\n            } else {\n                selection = options.first\n            }\n        } label: { EmptyView() }\n            .opacity(0)\n            .keyboardShortcut(.downArrow, modifiers: [])\n            .accessibilityLabel(\"Select Down\")\n    }\n}\n\nstruct PreviewToggle: View {\n    @Binding var previewVisible: Bool\n\n    var body: some View {\n        ZStack {\n            Rectangle()\n                .fill(Color(NSColor.secondaryLabelColor))\n                .frame(width: previewVisible ? 12 : 14, height: 1)\n                .offset(CGSize(width: 0, height: -2.5))\n            if !previewVisible {\n                Rectangle()\n                    .fill(Color(NSColor.secondaryLabelColor))\n                    .frame(width: 1, height: 8)\n                    .offset(CGSize(width: -2.5, height: 2))\n            }\n            RoundedRectangle(cornerRadius: 2, style: .continuous)\n                .strokeBorder(Color(NSColor.secondaryLabelColor), lineWidth: 1)\n                .frame(width: previewVisible ? 14 : 16, height: 14)\n        }\n        .frame(width: 16, height: 16)\n        .padding(4)\n        .contentShape(Rectangle())\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Views/SegmentedControl.swift",
    "content": "//\n//  SegmentedControl.swift\n//  CodeEditModules/CodeEditUI\n//\n//  Created by Lukas Pistrol on 31.03.22.\n//\n\nimport SwiftUI\n\n/// A view that creates a segmented control from an array of text labels.\nstruct SegmentedControl: View {\n    private var options: [String]\n    private var prominent: Bool\n\n    @Binding private var preselectedIndex: Int\n\n    /// A view that creates a segmented control from an array of text labels.\n    /// - Parameters:\n    ///   - selection: The index of the current selected item.\n    ///   - options: the options to display as an array of strings.\n    ///   - prominent: A Bool indicating whether to use a prominent appearance instead\n    ///   of the muted selection color. Defaults to `false`.\n    init(\n        _ selection: Binding<Int>,\n        options: [String],\n        prominent: Bool = false\n    ) {\n        self._preselectedIndex = selection\n        self.options = options\n        self.prominent = prominent\n    }\n\n    var body: some View {\n        HStack(spacing: 4) {\n            ForEach(options.indices, id: \\.self) { index in\n                SegmentedControlItem(\n                    label: options[index],\n                    active: preselectedIndex == index,\n                    action: {\n                        preselectedIndex = index\n                    },\n                    prominent: prominent\n                )\n\n            }\n        }\n        .frame(height: 20)\n    }\n}\n\nstruct SegmentedControlItem: View {\n    private let color: Color = Color(nsColor: .selectedControlColor)\n    let label: String\n    let active: Bool\n    let action: () -> Void\n    let prominent: Bool\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @Environment(\\.controlActiveState)\n    private var activeState\n\n    @State var isHovering: Bool = false\n\n    @State var isPressing: Bool = false\n\n    var body: some View {\n        Text(label)\n            .font(.subheadline)\n            .foregroundColor(textColor)\n            .opacity(textOpacity)\n            .frame(height: 20)\n            .padding(.horizontal, 7.5)\n            .background(\n                background\n            )\n            .clipShape(RoundedRectangle(cornerRadius: 5))\n            .onTapGesture {\n                action()\n            }\n            .onHover { hover in\n                isHovering = hover\n            }\n            .pressAction {\n                isPressing = true\n            } onRelease: {\n                isPressing = false\n            }\n\n    }\n\n    private var textColor: Color {\n        if prominent {\n            return active\n            ? .white\n            : .primary\n        } else {\n            return active\n            ? colorScheme == .dark ? .white : .accentColor\n            : .primary\n        }\n    }\n\n    private var textOpacity: Double {\n        if prominent {\n            return activeState != .inactive ? 1 : active ? 1 : 0.3\n        } else {\n            return activeState != .inactive ? 1 : active ? 0.5 : 0.3\n        }\n    }\n\n    @ViewBuilder private var background: some View {\n        if prominent {\n            if active {\n                Color.accentColor.opacity(activeState != .inactive ? 1 : 0.5)\n            } else {\n                Color(nsColor: colorScheme == .dark ? .white : .black)\n                .opacity(isPressing ? 0.10 : isHovering ? 0.05 : 0)\n            }\n        } else {\n            if active {\n                color.opacity(isPressing ? 1 : activeState != .inactive ? 0.75 : 0.5)\n            } else {\n                Color(nsColor: colorScheme == .dark ? .white : .black)\n                .opacity(isPressing ? 0.10 : isHovering ? 0.05 : 0)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Views/SettingsTextEditor.swift",
    "content": "//\n//  SettingsTextEditor.swift\n//  \n//\n//  Created by Andrey Plotnikov on 07.05.2022.\n//\n\nimport Foundation\nimport SwiftUI\n\nstruct SettingsTextEditor: View {\n    @State private var isFocus: Bool = false\n\n    @Binding var text: String\n\n    init(text: Binding<String>) {\n        self._text = text\n    }\n\n    var body: some View {\n        Representable(text: $text, isFocused: $isFocus)\n            .overlay(focusOverlay)\n    }\n\n    private var focusOverlay: some View {\n      Rectangle().stroke(Color.accentColor.opacity(isFocus ? 0.4 : 0), lineWidth: 2)\n    }\n}\n\nprivate extension SettingsTextEditor {\n    struct Representable: NSViewRepresentable {\n\n        @Binding var text: String\n        @Binding var isFocused: Bool\n\n        func makeNSView(context: Context) -> NSScrollView {\n            let scrollView = NSTextView.scrollableTextView()\n            scrollView.verticalScroller?.alphaValue = 0\n            let textView = scrollView.documentView as? NSTextView\n            textView?.backgroundColor = .windowBackgroundColor\n            textView?.isEditable = true\n            textView?.delegate = context.coordinator\n            textView?.string = text\n            return scrollView\n        }\n\n        func updateNSView(_ nsView: NSScrollView, context: Context) {\n\n        }\n\n        func makeCoordinator() -> Coordinator {\n            Coordinator(parent: self)\n        }\n\n        class Coordinator: NSObject, NSTextViewDelegate {\n            var parent: Representable\n\n            init(parent: Representable) {\n                self.parent = parent\n            }\n\n            func textDidBeginEditing(_ notification: Notification) {\n                parent.isFocused = true\n            }\n\n            func textDidEndEditing(_ notification: Notification) {\n                parent.isFocused = false\n            }\n\n            func textDidChange(_ notification: Notification) {\n                guard let textView = notification.object as? NSTextView else {\n                    return\n                }\n                // Update text\n                self.parent.text = textView.string\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift",
    "content": "//\n//  ToolbarBranchPicker.swift\n//  CodeEditModules/CodeEditUI\n//\n//  Created by Lukas Pistrol on 21.04.22.\n//\n\nimport SwiftUI\nimport CodeEditSymbols\nimport Combine\n\n/// A view that pops up a branch picker.\nstruct ToolbarBranchPicker: View {\n    private weak var workspaceFileManager: CEWorkspaceFileManager?\n    private weak var sourceControlManager: SourceControlManager?\n\n    @Environment(\\.controlActiveState)\n    private var controlActive\n\n    @State private var isHovering: Bool = false\n    @State private var displayPopover: Bool = false\n    @State private var currentBranch: GitBranch?\n\n    /// Initializes the ``ToolbarBranchPicker`` with an instance of a `WorkspaceClient`\n    /// - Parameter workspace: An instance of the current `WorkspaceClient`\n    init(\n        workspaceFileManager: CEWorkspaceFileManager?\n    ) {\n        self.workspaceFileManager = workspaceFileManager\n        self.sourceControlManager = workspaceFileManager?.sourceControlManager\n    }\n\n    var body: some View {\n        HStack(alignment: .center, spacing: 7) {\n            Group {\n                if currentBranch != nil {\n                    Image(symbol: \"branch\")\n                } else {\n                    Image(systemName: \"folder.fill.badge.gearshape\")\n                }\n            }\n            .foregroundColor(controlActive == .inactive ? inactiveColor : .secondary)\n            .font(.system(size: 14))\n            .imageScale(.medium)\n            .frame(width: 17, height: 17)\n            VStack(alignment: .leading, spacing: 0) {\n                Text(title)\n                    .font(.headline)\n                    .foregroundColor(controlActive == .inactive ? inactiveColor : .primary)\n                    .frame(height: 16)\n                    .help(title)\n                if let currentBranch {\n                    Menu(content: {\n                        if let sourceControlManager = workspaceFileManager?.sourceControlManager {\n                            PopoverView(sourceControlManager: sourceControlManager)\n                        }\n                    }, label: {\n                        Text(currentBranch.name)\n                            .font(.subheadline)\n                            .foregroundColor(controlActive == .inactive ? inactiveColor : .gray)\n                            .frame(height: 11)\n                    })\n                    .menuIndicator(isHovering ? .visible : .hidden)\n                    .buttonStyle(.borderless)\n                    .padding(.leading, -3)\n                    .padding(.bottom, 2)\n                }\n            }\n        }\n        .onHover { active in\n            isHovering = active\n        }\n        .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { (_) in\n            if self.currentBranch != nil {\n                Task {\n                    await sourceControlManager?.refreshCurrentBranch()\n                }\n            }\n        }\n        .onReceive(\n            self.sourceControlManager?.$currentBranch.eraseToAnyPublisher() ??\n            Empty().eraseToAnyPublisher()\n        ) { branch in\n            self.currentBranch = branch\n        }\n        .task {\n            if Settings.shared.preferences.sourceControl.general.sourceControlIsEnabled {\n                await self.sourceControlManager?.refreshCurrentBranch()\n                await self.sourceControlManager?.refreshBranches()\n            }\n        }\n        .if(.tahoe) {\n            $0.padding(.leading, 10).frame(minWidth: 140)\n        }\n    }\n\n    private var inactiveColor: Color {\n        Color(nsColor: .disabledControlTextColor)\n    }\n\n    private var title: String {\n        workspaceFileManager?.folderUrl.lastPathComponent ?? \"Empty\"\n    }\n\n    // MARK: Popover View\n\n    /// A popover view that appears once the branch picker is tapped.\n    ///\n    /// It displays the currently checked-out branch and all other local branches.\n    private struct PopoverView: View {\n        @ObservedObject var sourceControlManager: SourceControlManager\n\n        var body: some View {\n            VStack(alignment: .leading) {\n                if let currentBranch = sourceControlManager.currentBranch {\n                    Section {\n                        headerLabel(\"Current Branch\")\n                        BranchCell(sourceControlManager: sourceControlManager, branch: currentBranch, active: true)\n                    }\n                }\n\n                let branches = sourceControlManager.orderedLocalBranches\n                    .filter({ $0 != sourceControlManager.currentBranch })\n                let branchesGroups = branches.reduce(into: [String: GitBranchesGroup]()) { result, branch in\n                    guard let branchPrefix = branch.name.components(separatedBy: \"/\").first else {\n                        return\n                    }\n\n                    result[\n                        branchPrefix.lowercased(),\n                        default: GitBranchesGroup(name: branchPrefix, branches: [])\n                    ].branches.append(branch)\n                }\n\n                if !branches.isEmpty {\n                    Section {\n                        headerLabel(\"Branches\")\n                        ForEach(branchesGroups.keys.sorted(), id: \\.self) { branchGroupPrefix in\n                            if let group = branchesGroups[branchGroupPrefix] {\n                                if !group.shouldNest {\n                                    BranchCell(\n                                        sourceControlManager: sourceControlManager,\n                                        branch: group.branches.first!\n                                    )\n                                } else {\n                                    Menu(content: {\n                                        ForEach(group.branches, id: \\.self) { branch in\n                                            BranchCell(\n                                                sourceControlManager: sourceControlManager,\n                                                branch: branch,\n                                                title: String(\n                                                    branch.name.suffix(branch.name.count - branchGroupPrefix.count - 1)\n                                                )\n                                            )\n                                        }\n                                    }, label: {\n                                        HStack {\n                                            Image(systemName: \"folder\")\n                                            Text(group.name)\n                                        }\n                                    })\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n            .padding(.top, 10)\n            .padding(5)\n            .frame(width: 340)\n        }\n\n        func headerLabel(_ title: String) -> some View {\n            Text(title)\n                .font(.subheadline.bold())\n                .foregroundColor(.secondary)\n                .padding(.horizontal)\n                .padding(.vertical, 5)\n        }\n\n        // MARK: Branch Cell\n\n        /// A Button Cell that represents a branch in the branch picker\n        struct BranchCell: View {\n            let sourceControlManager: SourceControlManager\n            let branch: GitBranch\n            let active: Bool\n            let title: String?\n\n            init(\n                sourceControlManager: SourceControlManager,\n                branch: GitBranch,\n                active: Bool = false,\n                title: String? = nil\n            ) {\n                self.sourceControlManager = sourceControlManager\n                self.branch = branch\n                self.active = active\n                self.title = title\n            }\n\n            var body: some View {\n                Button {\n                    switchBranch()\n                } label: {\n                    HStack {\n                        if active {\n                            Image(systemName: \"checkmark.circle.fill\")\n                        } else {\n                            Image.branch\n                        }\n                        Text(self.title ?? branch.name)\n                    }\n                }\n            }\n\n            func switchBranch() {\n                Task {\n                    do {\n                        try await sourceControlManager.checkoutBranch(branch: branch)\n                    } catch {\n                        await sourceControlManager.showAlertForError(title: \"Failed to checkout\", error: error)\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Views/TrackableScrollView.swift",
    "content": "//\n//  TrackableScrollView.swift\n//  CodeEdit\n//\n//  Created by Lukas Pistrol on 19.01.23.\n//\n\n//  Inspired by SwiftUITrackableScrollView by Natchanon A.\n//  https://github.com/maxnatchanon/trackable-scroll-view\n\nimport SwiftUI\n\nprivate struct ScrollViewOffsetPreferenceKey: PreferenceKey {\n    typealias Value = [CGFloat]\n\n    static var defaultValue: [CGFloat] = [0]\n\n    static func reduce(value: inout [CGFloat], nextValue: () -> [CGFloat]) {\n        value.append(contentsOf: nextValue())\n    }\n}\n\nstruct TrackableScrollView<Content>: View where Content: View {\n    let axes: Axis.Set\n    let showIndicators: Bool\n    @Binding var contentOffset: CGFloat\n    @Binding var contentTrailingOffset: CGFloat?\n    let content: Content\n\n    init(\n        _ axes: Axis.Set = .vertical,\n        showIndicators: Bool = true,\n        contentOffset: Binding<CGFloat>,\n        @ViewBuilder content: () -> Content\n    ) {\n        self.axes = axes\n        self.showIndicators = showIndicators\n        self._contentOffset = contentOffset\n        self._contentTrailingOffset = Binding.constant(nil)\n        self.content = content()\n    }\n\n    init(\n        _ axes: Axis.Set = .vertical,\n        showIndicators: Bool = true,\n        contentOffset: Binding<CGFloat>,\n        contentTrailingOffset: Binding<CGFloat?>?,\n        @ViewBuilder content: () -> Content\n    ) {\n        self.axes = axes\n        self.showIndicators = showIndicators\n        self._contentOffset = contentOffset\n        self._contentTrailingOffset = contentTrailingOffset ?? Binding.constant(nil)\n        self.content = content()\n    }\n\n    var body: some View {\n        GeometryReader { outsideProxy in\n            ScrollView(self.axes, showsIndicators: self.showIndicators) {\n                ZStack(alignment: self.axes == .vertical ? .top : .leading) {\n                    GeometryReader { insideProxy in\n                        Color.clear\n                            .preference(\n                                key: ScrollViewOffsetPreferenceKey.self,\n                                value: [\n                                    self.calculateContentOffset(\n                                        fromOutsideProxy: outsideProxy,\n                                        insideProxy: insideProxy\n                                    ),\n                                    self.calculateContentTrailingOffset(\n                                        fromOutsideProxy: outsideProxy,\n                                        insideProxy: insideProxy\n                                    )\n                                ]\n                            )\n                    }\n                    VStack {\n                        self.content\n                    }\n                }\n            }\n            .onPreferenceChange(ScrollViewOffsetPreferenceKey.self) { value in\n                self.contentOffset = value[0]\n                if self.contentTrailingOffset != nil {\n                    self.contentTrailingOffset = value[1]\n                }\n            }\n        }\n    }\n\n    private func calculateContentOffset(\n        fromOutsideProxy outsideProxy: GeometryProxy,\n        insideProxy: GeometryProxy\n    ) -> CGFloat {\n        if axes == .vertical {\n            return insideProxy.frame(in: .global).minY - outsideProxy.frame(in: .global).minY\n        } else {\n            return insideProxy.frame(in: .global).minX - outsideProxy.frame(in: .global).minX\n        }\n    }\n\n    private func calculateContentTrailingOffset(\n        fromOutsideProxy outsideProxy: GeometryProxy,\n        insideProxy: GeometryProxy\n    ) -> CGFloat {\n        if axes == .vertical {\n            return insideProxy.frame(in: .global).maxY - outsideProxy.frame(in: .global).maxY\n        } else {\n            return insideProxy.frame(in: .global).maxX - outsideProxy.frame(in: .global).maxX\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Views/WorkspacePanelTabBar.swift",
    "content": "//\n//  WorkspacePanelTabBar.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 5/25/23.\n//\n\nimport SwiftUI\n\nprotocol WorkspacePanelTab: View, Identifiable, Hashable {\n    var title: String { get }\n    var systemImage: String { get }\n}\n\nstruct WorkspacePanelTabBar<Tab: WorkspacePanelTab>: View {\n    @Binding var items: [Tab]\n    @Binding var selection: Tab?\n\n    var position: SettingsData.SidebarTabBarPosition\n\n    @State private var tabLocations: [Tab: CGRect] = [:]\n    @State private var tabWidth: [Tab: CGFloat] = [:]\n    @State private var tabOffsets: [Tab: CGFloat] = [:]\n\n    /// The tab currently being dragged.\n    ///\n    /// It will be `nil` when there is no tab dragged currently.\n    @State private var draggingTab: Tab?\n\n    /// The start location of dragging.\n    ///\n    /// When there is no tab being dragged, it will be `nil`.\n    @State private var draggingStartLocation: CGFloat?\n\n    /// The last location of dragging.\n    ///\n    /// This is used to determine the dragging direction.\n    /// - TODO: Check if I can use `value.translation` instead.\n    @State private var draggingLastLocation: CGFloat?\n\n    var body: some View {\n        if position == .top {\n            topBody\n        } else {\n            sideBody\n        }\n    }\n\n    var topBody: some View {\n        GeometryReader { proxy in\n            iconsView(size: proxy.size)\n                .frame(maxWidth: .infinity, maxHeight: .infinity)\n                .animation(.default, value: items)\n        }\n        .clipped()\n        .frame(maxWidth: .infinity, idealHeight: 27)\n        .fixedSize(horizontal: false, vertical: true)\n    }\n\n    var sideBody: some View {\n        GeometryReader { proxy in\n            iconsView(size: proxy.size)\n                .padding(.vertical, 5)\n                .frame(maxWidth: .infinity, maxHeight: .infinity)\n                .animation(.default, value: items)\n        }\n        .clipped()\n        .frame(idealWidth: 40, maxHeight: .infinity)\n        .fixedSize(horizontal: true, vertical: false)\n    }\n\n    @ViewBuilder\n    func iconsView(size: CGSize) -> some View {\n        let layout = position == .top\n            ? AnyLayout(HStackLayout(spacing: 0))\n            : AnyLayout(VStackLayout(spacing: 0))\n        layout {\n            ForEach(items) { tab in\n                makeIcon(tab: tab, size: size)\n                    .offset(\n                        x: (position == .top) ? (tabOffsets[tab] ?? 0) : 0,\n                        y: (position == .side) ? (tabOffsets[tab] ?? 0) : 0\n                    )\n                    .background(makeTabItemGeometryReader(tab: tab))\n                    .simultaneousGesture(makeAreaTabDragGesture(tab: tab))\n            }\n            if position == .side {\n                Spacer()\n            }\n        }\n    }\n\n    private func makeIcon(\n        tab: Tab,\n        scale: Image.Scale = .medium,\n        size: CGSize\n    ) -> some View {\n        Button {\n            selection = tab\n        } label: {\n            getSafeImage(named: tab.systemImage, accessibilityDescription: tab.title)\n                .font(.system(size: 12.5))\n                .symbolVariant(tab == selection ? .fill : .none)\n                .help(tab.title)\n        }\n        .buttonStyle(\n            .icon(\n                isActive: tab == selection,\n                size: CGSize(\n                    width: position == .side ? 40 : 24,\n                    height: position == .side ? 28 : size.height\n                )\n            )\n        )\n        .focusable(false)\n        .accessibilityIdentifier(\"WorkspacePanelTab-\\(tab.title)\")\n        .accessibilityLabel(tab.title)\n    }\n\n    private func makeAreaTabDragGesture(tab: Tab) -> some Gesture {\n        DragGesture(minimumDistance: 2, coordinateSpace: .global)\n            .onChanged({ value in\n                if draggingTab != tab {\n                    initializeDragGesture(value: value, for: tab)\n                }\n\n                // Get the current cursor location\n                let currentLocation = (position == .top) ? value.location.x : value.location.y\n                guard let startLocation = draggingStartLocation,\n                      let currentIndex = items.firstIndex(of: tab),\n                      let currentTabWidth = tabWidth[tab],\n                      let lastLocation = draggingLastLocation\n                else { return }\n\n                let dragDifference = currentLocation - lastLocation\n                tabOffsets[tab] = currentLocation - startLocation\n\n                // Check for swaps between adjacent tabs\n                // Left tab\n                swapTab(\n                    tab: tab,\n                    currentIndex: currentIndex,\n                    currentLocation: currentLocation,\n                    dragDifference: dragDifference,\n                    currentTabWidth: currentTabWidth,\n                    direction: .previous\n                )\n                // Right tab\n                swapTab(\n                    tab: tab,\n                    currentIndex: currentIndex,\n                    currentLocation: currentLocation,\n                    dragDifference: dragDifference,\n                    currentTabWidth: currentTabWidth,\n                    direction: .next\n                )\n\n                // Update the last dragging location if there's enough offset\n                let currentLocationOnAxis = ((position == .top) ? value.location.x : value.location.y)\n                if draggingLastLocation == nil || abs(currentLocationOnAxis - draggingLastLocation!) >= 10 {\n                    draggingLastLocation = (position == .top) ? value.location.x : value.location.y\n                }\n            })\n            .onEnded({ _ in\n                draggingStartLocation = nil\n                draggingLastLocation = nil\n                withAnimation(.easeInOut(duration: 0.25)) {\n                    tabOffsets = [:]\n                }\n                DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {\n                    draggingTab = nil\n                }\n            })\n    }\n\n    private func initializeDragGesture(value: DragGesture.Value, for tab: Tab) {\n        draggingTab = tab\n        let initialLocation = position == .top ? value.startLocation.x : value.startLocation.y\n        draggingStartLocation = initialLocation\n        draggingLastLocation = initialLocation\n    }\n\n    enum SwapDirection {\n        case previous\n        case next\n    }\n\n    // swiftlint:disable:next function_parameter_count\n    private func swapTab(\n        tab: Tab,\n        currentIndex: Int,\n        currentLocation: CGFloat,\n        dragDifference: CGFloat,\n        currentTabWidth: CGFloat,\n        direction: SwapDirection\n    ) {\n        // Determine the index to swap with based on direction\n        var swapIndex: Int?\n        if direction == .previous {\n            if currentIndex > 0 {\n                swapIndex = currentIndex - 1\n            }\n        } else {\n            if currentIndex < items.count - 1 {\n                swapIndex = currentIndex + 1\n            }\n        }\n\n        // Validate the drag direction\n        let isValidDragDir = (direction == .previous && dragDifference < 0) ||\n                             (direction == .next && dragDifference > 0)\n        guard let swapIndex = swapIndex, isValidDragDir else { return }\n\n        // Get info about the tab to swap with\n        let swapTab = items[swapIndex]\n        guard let swapTabLocation = tabLocations[swapTab],\n              let swapTabWidth = tabWidth[swapTab]\n        else { return }\n\n        let isWithinBounds: Bool\n        if position == .top {\n            isWithinBounds = direction == .previous ?\n                isWithinPrevTopBounds(currentLocation, swapTabLocation, swapTabWidth) :\n                isWithinNextTopBounds(currentLocation, swapTabLocation, swapTabWidth, currentTabWidth)\n        } else {\n            isWithinBounds = direction == .previous ?\n                isWithinPrevBottomBounds(currentLocation, swapTabLocation, swapTabWidth) :\n                isWithinNextBottomBounds(currentLocation, swapTabLocation, swapTabWidth, currentTabWidth)\n        }\n\n        // Swap tab positions\n        if isWithinBounds {\n            let changing = swapTabWidth - 1\n            draggingStartLocation! += direction == .previous ? -changing : changing\n            tabOffsets[tab]! += direction == .previous ? changing : -changing\n            items.swapAt(currentIndex, swapIndex)\n        }\n    }\n\n    private func isWithinPrevTopBounds(\n        _ curLocation: CGFloat, _ swapLocation: CGRect, _ swapWidth: CGFloat\n    ) -> Bool {\n        return curLocation < max(\n            swapLocation.maxX - swapWidth * 0.1,\n            swapLocation.minX + swapWidth * 0.9\n        )\n    }\n\n    private func isWithinNextTopBounds(\n        _ curLocation: CGFloat, _ swapLocation: CGRect, _ swapWidth: CGFloat, _ curWidth: CGFloat\n    ) -> Bool {\n        return curLocation > min(\n            swapLocation.minX + swapWidth * 0.1,\n            swapLocation.maxX - curWidth * 0.9\n        )\n    }\n\n    private func isWithinPrevBottomBounds(\n        _ curLocation: CGFloat, _ swapLocation: CGRect, _ swapWidth: CGFloat\n    ) -> Bool {\n        return curLocation < max(\n            swapLocation.maxY - swapWidth * 0.1,\n            swapLocation.minY + swapWidth * 0.9\n        )\n    }\n\n    private func isWithinNextBottomBounds(\n        _ curLocation: CGFloat, _ swapLocation: CGRect, _ swapWidth: CGFloat, _ curWidth: CGFloat\n    ) -> Bool {\n        return curLocation > min(\n            swapLocation.minY + swapWidth * 0.1,\n            swapLocation.maxY - curWidth * 0.9\n        )\n    }\n\n    private func makeTabItemGeometryReader(tab: Tab) -> some View {\n        GeometryReader { geometry in\n            Rectangle()\n                .foregroundColor(.clear)\n                .onAppear {\n                    self.tabWidth[tab] = (position == .top) ? geometry.size.width : geometry.size.height\n                    self.tabLocations[tab] = geometry.frame(in: .global)\n                }\n                .onChange(of: geometry.frame(in: .global)) { _, newFrame in\n                    self.tabLocations[tab] = newFrame\n                }\n                .onChange(of: geometry.size.width) { _, newWidth in\n                    self.tabWidth[tab] = newWidth\n                }\n        }\n    }\n\n    private func getSafeImage(named: String, accessibilityDescription: String?) -> Image {\n        // We still use the NSImage init to check if a symbol with the name exists.\n        if NSImage(systemSymbolName: named, accessibilityDescription: nil) != nil {\n            return Image(systemName: named)\n        } else {\n            return Image(symbol: named)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/CodeEditUI/Views/WorkspacePanelView.swift",
    "content": "//\n//  WorkspacePanelView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 1/4/25.\n//\n\nimport SwiftUI\n\nstruct WorkspacePanelView<Tab: WorkspacePanelTab, ViewModel: ObservableObject>: View {\n    @ObservedObject var viewModel: ViewModel\n    @Binding var selectedTab: Tab?\n    @Binding var tabItems: [Tab]\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    var sidebarPosition: SettingsData.SidebarTabBarPosition\n    var darkDivider: Bool\n\n    init(\n        viewModel: ViewModel,\n        selectedTab: Binding<Tab?>,\n        tabItems: Binding<[Tab]>,\n        sidebarPosition: SettingsData.SidebarTabBarPosition,\n        darkDivider: Bool = false\n    ) {\n        self.viewModel = viewModel\n        self._selectedTab = selectedTab\n        self._tabItems = tabItems\n        self.sidebarPosition = sidebarPosition\n        self.darkDivider = darkDivider\n    }\n\n    var body: some View {\n        VStack(spacing: 0) {\n            if let selection = selectedTab {\n                selection\n            } else {\n                CEContentUnavailableView(\"No Selection\")\n            }\n        }\n        .safeAreaInset(edge: .leading, spacing: 0) {\n            if sidebarPosition == .side {\n                HStack(spacing: 0) {\n                    WorkspacePanelTabBar(items: $tabItems, selection: $selectedTab, position: sidebarPosition)\n                    Divider()\n                        .overlay(Color(nsColor: darkDivider && colorScheme == .dark ? .black : .clear))\n                }\n            }\n        }\n        .safeAreaInset(edge: .top, spacing: 0) {\n            if sidebarPosition == .top {\n                VStack(spacing: 0) {\n                    Divider()\n                    WorkspacePanelTabBar(items: $tabItems, selection: $selectedTab, position: sidebarPosition)\n                    Divider()\n                }\n            } else if !darkDivider {\n                Divider()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Commands/ViewModels/QuickActionsViewModel.swift",
    "content": "//\n//  CommandPaletteViewModel.swift\n//  CodeEdit\n//\n//  Created by Alex on 25.05.2022.\n//\n\nimport SwiftUI\n\n/// Simple state class for command palette view. Contains currently selected command,\n/// query text and list of filtered commands\nfinal class QuickActionsViewModel: ObservableObject {\n\n    @Published var commandQuery: String = \"\"\n\n    @Published var selected: Command?\n\n    @Published var isShowingCommandsList: Bool = true\n\n    @Published var filteredCommands: [Command] = []\n\n    init() {}\n\n    func reset() {\n        commandQuery = \"\"\n        selected = nil\n        filteredCommands = CommandManager.shared.commands\n    }\n\n    func fetchMatchingCommands(val: String) {\n        if val == \"\" {\n            self.filteredCommands = CommandManager.shared.commands\n            return\n        }\n        self.filteredCommands = CommandManager.shared.commands.filter { $0.title.localizedCaseInsensitiveContains(val) }\n        self.selected = self.filteredCommands.first\n    }\n\n    func highlight(_ commandTitle: String) -> NSAttributedString {\n        let attribText = NSMutableAttributedString(string: commandTitle)\n        let range: NSRange = attribText.mutableString.range(\n            of: self.commandQuery,\n            options: NSString.CompareOptions.caseInsensitive\n        )\n        attribText.addAttribute(.foregroundColor, value: NSColor(Color(.labelColor)), range: range)\n        attribText.addAttribute(.font, value: NSFont.boldSystemFont(ofSize: NSFont.systemFontSize), range: range)\n\n        return attribText\n    }\n\n}\n"
  },
  {
    "path": "CodeEdit/Features/Commands/Views/QuickActionsView.swift",
    "content": "//\n//  CommandPaletteView.swift\n//  CodeEdit\n//\n//  Created by Alex Sinelnikov on 24.05.2022.\n//\n\nimport SwiftUI\n\n/// Quick actions view\nstruct QuickActionsView: View {\n\n    @Environment(\\.colorScheme)\n    private var colorScheme: ColorScheme\n\n    @ObservedObject private var state: QuickActionsViewModel\n\n    @ObservedObject private var commandManager: CommandManager = .shared\n\n    @State private var monitor: Any?\n\n    @State private var selectedItem: Command?\n\n    private let closePalette: () -> Void\n\n    init(state: QuickActionsViewModel, closePalette: @escaping () -> Void) {\n        self.state = state\n        self.closePalette = closePalette\n        state.filteredCommands = commandManager.commands\n    }\n\n    func callHandler(command: Command) {\n        closePalette()\n        command.closureWrapper()\n        selectedItem = nil\n        state.commandQuery = \"\"\n        state.filteredCommands = []\n    }\n\n    func onQueryChange(text: String) {\n        state.commandQuery = text\n        state.fetchMatchingCommands(val: text)\n    }\n\n    var body: some View {\n        SearchPanelView<QuickSearchResultLabel, EmptyView, Command>(\n            title: \"Commands\",\n            image: Image(systemName: \"magnifyingglass\"),\n            options: $state.filteredCommands,\n            text: $state.commandQuery,\n            alwaysShowOptions: true,\n            optionRowHeight: 30\n        ) { command in\n            QuickSearchResultLabel(\n                labelName: command.title,\n                charactersToHighlight: [],\n                nsLabelName: state.highlight(command.title)\n            )\n        } onRowClick: { command in\n            callHandler(command: command)\n        } onClose: {\n            closePalette()\n        }\n        .onReceive(state.$commandQuery.debounce(for: 0.2, scheduler: DispatchQueue.main)) { _ in\n            state.fetchMatchingCommands(val: state.commandQuery)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/CodeFileDocument/CodeFileDocument.swift",
    "content": "//\n//  CodeFileDocument.swift\n//  CodeEditModules/CodeFile\n//\n//  Created by Rehatbir Singh on 12/03/2022.\n//\n\nimport AppKit\nimport Foundation\nimport SwiftUI\nimport UniformTypeIdentifiers\nimport CodeEditSourceEditor\nimport CodeEditTextView\nimport CodeEditLanguages\nimport Combine\nimport OSLog\nimport TextStory\n\nenum CodeFileError: Error {\n    case failedToDecode\n    case failedToEncode\n    case fileTypeError\n}\n\n@objc(CodeFileDocument)\nfinal class CodeFileDocument: NSDocument, ObservableObject {\n    struct OpenOptions {\n        let cursorPositions: [CursorPosition]\n    }\n\n    static let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? \"\", category: \"CodeFileDocument\")\n\n    /// Sent when the document is opened. The document will be sent in the notification's object.\n    static let didOpenNotification = Notification.Name(rawValue: \"CodeFileDocument.didOpen\")\n    /// Sent when the document is closed. The document's `fileURL` will be sent in the notification's object.\n    static let didCloseNotification = Notification.Name(rawValue: \"CodeFileDocument.didClose\")\n\n    /// The text content of the document, stored as a text storage\n    ///\n    /// This is intentionally not a `@Published` variable. If it were published, SwiftUI would do a string\n    /// compare each time the contents are updated, which could cause a hang on each keystroke if the file is large\n    /// enough.\n    ///\n    /// To receive notifications for content updates, subscribe to one of the publishers on ``contentCoordinator``.\n    var content: NSTextStorage?\n\n    /// The string encoding of the original file. Used to save the file back to the encoding it was loaded from.\n    var sourceEncoding: FileEncoding?\n\n    /// The coordinator to use to subscribe to edit events and cursor location events.\n    /// See ``CodeEditSourceEditor/CombineCoordinator``.\n    @Published var contentCoordinator: CombineCoordinator = CombineCoordinator()\n\n    /// Used to override detected languages.\n    @Published var language: CodeLanguage?\n\n    /// Document-specific overridden indent option.\n    @Published var indentOption: SettingsData.TextEditingSettings.IndentOption?\n\n    /// Document-specific overridden tab width.\n    @Published var defaultTabWidth: Int?\n\n    /// Document-specific overridden line wrap preference.\n    @Published var wrapLines: Bool?\n\n    /// Set up by ``LanguageServer``, conforms this type to ``LanguageServerDocument``.\n    @Published var languageServerObjects: LanguageServerDocumentObjects<CodeFileDocument> = .init()\n\n    /// The type of data this file document contains.\n    ///\n    /// If its text content is not nil, a `text` UTType is returned.\n    ///\n    /// - Note: The UTType doesn't necessarily mean the file extension, it can be the MIME\n    /// type or any other form of data representation.\n    var utType: UTType? {\n        if content != nil {\n            return .text\n        }\n\n        guard let fileType, let type = UTType(fileType) else {\n            return nil\n        }\n\n        return type\n    }\n\n    /// Specify options for opening the file such as the initial cursor positions.\n    /// Nulled by ``CodeFileView`` on first load.\n    var openOptions: OpenOptions?\n\n    private let isDocumentEditedSubject = PassthroughSubject<Bool, Never>()\n\n    /// Publisher for isDocumentEdited property\n    var isDocumentEditedPublisher: AnyPublisher<Bool, Never> {\n        isDocumentEditedSubject.eraseToAnyPublisher()\n    }\n\n    /// A lock that ensures autosave scheduling happens correctly.\n    private var autosaveTimerLock: NSLock = NSLock()\n    /// Timer used to schedule autosave intervals.\n    private var autosaveTimer: Timer?\n\n    // MARK: - NSDocument\n\n    override static var autosavesInPlace: Bool {\n        Settings.shared.preferences.general.isAutoSaveOn\n    }\n\n    override var autosavingFileType: String? {\n        Settings.shared.preferences.general.isAutoSaveOn\n            ? fileType\n            : nil\n    }\n\n    override func makeWindowControllers() {\n        let window = NSWindow(\n            contentRect: NSRect(x: 0, y: 0, width: 750, height: 800),\n            styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],\n            backing: .buffered, defer: false\n        )\n        let windowController = NSWindowController(window: window)\n        if let fileURL {\n            windowController.shouldCascadeWindows = false\n            windowController.windowFrameAutosaveName = fileURL.path\n        }\n        addWindowController(windowController)\n\n        window.contentView = NSHostingView(rootView: SettingsInjector {\n            WindowCodeFileView(codeFile: self)\n        })\n\n        window.makeKeyAndOrderFront(nil)\n\n        if let fileURL, UserDefaults.standard.object(forKey: \"NSWindow Frame \\(fileURL.path)\") == nil {\n            window.center()\n        }\n    }\n\n    // MARK: - Data\n\n    override func data(ofType _: String) throws -> Data {\n        guard let sourceEncoding, let data = (content?.string as NSString?)?.data(using: sourceEncoding.nsValue) else {\n            Self.logger.error(\"Failed to encode contents to \\(self.sourceEncoding.debugDescription)\")\n            throw CodeFileError.failedToEncode\n        }\n        return data\n    }\n\n    // MARK: - Read\n\n    /// This function is used for decoding files.\n    /// It should not throw error as unsupported files can still be opened by QLPreviewView.\n    override func read(from data: Data, ofType _: String) throws {\n        var nsString: NSString?\n        let rawEncoding = NSString.stringEncoding(\n            for: data,\n            encodingOptions: [\n                .allowLossyKey: false, // Fail if using lossy encoding.\n                .suggestedEncodingsKey: FileEncoding.allCases.map { $0.nsValue },\n                .useOnlySuggestedEncodingsKey: true\n            ],\n            convertedString: &nsString,\n            usedLossyConversion: nil\n        )\n        guard let validEncoding = FileEncoding(rawEncoding), let nsString else {\n            Self.logger.error(\"Failed to read file from data using encoding: \\(rawEncoding)\")\n            return\n        }\n        self.sourceEncoding = validEncoding\n        if let content {\n            registerContentChangeUndo(fileURL: fileURL, nsString: nsString, content: content)\n            content.mutableString.setString(nsString as String)\n        } else {\n            self.content = NSTextStorage(string: nsString as String)\n        }\n        NotificationCenter.default.post(name: Self.didOpenNotification, object: self)\n    }\n\n    /// If this file is already open and being tracked by an undo manager, we register an undo mutation\n    /// of the entire contents. This allows the user to undo changes that occurred outside of CodeEdit\n    /// while the file was displayed in CodeEdit.\n    ///\n    /// - Note: This is inefficient memory-wise. We could do a diff of the file and only register the\n    ///         mutations that would recreate the diff. However, that would instead be CPU intensive.\n    ///         Tradeoffs.\n    private func registerContentChangeUndo(fileURL: URL?, nsString: NSString, content: NSTextStorage) {\n        guard let fileURL else { return }\n        // If there's an undo manager, register a mutation replacing the entire contents.\n        let mutation = TextMutation(\n            string: nsString as String,\n            range: NSRange(location: 0, length: content.length),\n            limit: content.length\n        )\n        let undoManager = self.findWorkspace()?.undoRegistration.managerIfExists(forFile: fileURL)\n        undoManager?.registerMutation(mutation)\n    }\n\n    // MARK: - Autosave\n\n    /// Triggered when change occurred\n    override func updateChangeCount(_ change: NSDocument.ChangeType) {\n        super.updateChangeCount(change)\n\n        if CodeFileDocument.autosavesInPlace {\n            return\n        }\n\n        self.isDocumentEditedSubject.send(self.isDocumentEdited)\n    }\n\n    /// Triggered when changes saved\n    override func updateChangeCount(withToken changeCountToken: Any, for saveOperation: NSDocument.SaveOperationType) {\n        super.updateChangeCount(withToken: changeCountToken, for: saveOperation)\n\n        if CodeFileDocument.autosavesInPlace {\n            return\n        }\n\n        self.isDocumentEditedSubject.send(self.isDocumentEdited)\n    }\n\n    /// If ``hasUnautosavedChanges`` is `true` and an autosave has not already been scheduled, schedules a new autosave.\n    /// If ``hasUnautosavedChanges`` is `false`, cancels any scheduled timers and returns.\n    ///\n    /// All operations are done with the ``autosaveTimerLock`` acquired (including the scheduled autosave) to ensure\n    /// correct timing when scheduling or cancelling timers.\n    override func scheduleAutosaving() {\n        autosaveTimerLock.withLock {\n            if self.hasUnautosavedChanges {\n                guard autosaveTimer == nil else { return }\n                autosaveTimer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { [weak self] timer in\n                    self?.autosaveTimerLock.withLock {\n                        guard timer.isValid else { return }\n                        self?.autosaveTimer = nil\n                        self?.autosave(withDelegate: nil, didAutosave: nil, contextInfo: nil)\n                    }\n                }\n            } else {\n                autosaveTimer?.invalidate()\n                autosaveTimer = nil\n            }\n        }\n    }\n\n    // MARK: - External Changes\n\n    /// Handle the notification that the represented file item changed.\n    ///\n    /// We check if a file has been modified and can be read again to display to the user.\n    /// To determine if a file has changed, we check the modification date. If it's different from the stored one,\n    /// we continue.\n    /// To determine if we can reload the file, we check if the document has outstanding edits. If not, we reload the\n    /// file.\n    override func presentedItemDidChange() {\n        if fileModificationDate != getModificationDate() {\n            guard isDocumentEdited else {\n                fileModificationDate = getModificationDate()\n                if let fileURL, let fileType {\n                    // This blocks the presented item thread intentionally. If we don't wait, we'll receive more updates\n                    // that the file has changed and we'll end up dispatching multiple reads.\n                    // The presented item thread expects this operation to by synchronous anyways.\n\n                    // https://github.com/CodeEditApp/CodeEdit/issues/2091\n                    // We can't use `.asyncAndWait` on Ventura as it seems the symbol is missing on that platform.\n                    // Could be just for x86 machines.\n                    DispatchQueue.main.sync {\n                        try? self.read(from: fileURL, ofType: fileType)\n                    }\n                }\n                return\n            }\n        }\n\n        super.presentedItemDidChange()\n    }\n\n    /// Helper to find the last modified date of the represented file item.\n    /// \n    /// Different from `NSDocument.fileModificationDate`. This returns the *current* modification date, whereas the\n    /// alternative stores the date that existed when we last read the file.\n    private func getModificationDate() -> Date? {\n        guard let path = fileURL?.absolutePath else { return nil }\n        return try? FileManager.default.attributesOfItem(atPath: path)[.modificationDate] as? Date\n    }\n\n    // MARK: - Close\n\n    override func close() {\n        super.close()\n        NotificationCenter.default.post(name: Self.didCloseNotification, object: fileURL)\n    }\n\n    override func save(_ sender: Any?) {\n        guard let fileURL else {\n            super.save(sender)\n            return\n        }\n\n        do {\n            // Get parent directory for cases when entire folders were deleted – and recreate them as needed\n            let directory = fileURL.deletingLastPathComponent()\n            try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil)\n\n            super.save(sender)\n        } catch {\n            presentError(error)\n        }\n    }\n\n    override func fileNameExtension(\n        forType typeName: String,\n        saveOperation: NSDocument.SaveOperationType\n    ) -> String? {\n        guard let fileTypeName = Self.fileTypeExtension[typeName] else {\n            return super.fileNameExtension(forType: typeName, saveOperation: saveOperation)\n        }\n        return fileTypeName\n    }\n\n    /// Determines the code language of the document.\n    /// Use ``CodeFileDocument/language`` for the default value before using this. That property is used to override\n    /// the file's language.\n    /// - Returns: The detected code language.\n    func getLanguage() -> CodeLanguage {\n        guard let url = fileURL else {\n            return .default\n        }\n        return language ?? CodeLanguage.detectLanguageFrom(\n            url: url,\n            prefixBuffer: content?.string.getFirstLines(5),\n            suffixBuffer: content?.string.getLastLines(5)\n        )\n    }\n\n    func findWorkspace() -> WorkspaceDocument? {\n        fileURL?.findWorkspace()\n    }\n}\n\n// MARK: LanguageServerDocument\n\nextension CodeFileDocument: LanguageServerDocument {\n    /// A stable string to use when identifying documents with language servers.\n    /// Needs to be a valid URI, so always returns with the `file://` prefix to indicate it's a file URI.\n    var languageServerURI: String? {\n        fileURL?.lspURI\n    }\n}\n\nprivate extension CodeFileDocument {\n\n    static let fileTypeExtension: [String: String?] = [\n        \"public.make-source\": nil\n    ]\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/CodeFileDocument/FileEncoding.swift",
    "content": "//\n//  FileEncoding.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 5/31/24.\n//\n\nimport Foundation\n\nenum FileEncoding: CaseIterable {\n    case utf8\n    case utf16BE\n    case utf16LE\n\n    var nsValue: UInt {\n        switch self {\n        case .utf8:\n            return NSUTF8StringEncoding\n        case .utf16BE:\n            return NSUTF16BigEndianStringEncoding\n        case .utf16LE:\n            return NSUTF16LittleEndianStringEncoding\n        }\n    }\n\n    init?(_ int: UInt) {\n        switch int {\n        case NSUTF8StringEncoding:\n            self = .utf8\n        case NSUTF16BigEndianStringEncoding:\n            self = .utf16BE\n        case NSUTF16LittleEndianStringEncoding:\n            self = .utf16LE\n        default:\n            return nil\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/Controllers/CodeEditDocumentController.swift",
    "content": "//\n//  CodeEditDocumentController.swift\n//  CodeEdit\n//\n//  Created by Pavel Kasila on 17.03.22.\n//\n\nimport Cocoa\nimport SwiftUI\nimport WelcomeWindow\n\nfinal class CodeEditDocumentController: NSDocumentController {\n    @Environment(\\.openWindow)\n    private var openWindow\n\n    @Service var lspService: LSPService\n\n    private let fileManager = FileManager.default\n\n    @MainActor\n    func createAndOpenNewDocument(onCompletion: @escaping () -> Void) {\n        guard let newDocumentUrl = self.newDocumentUrl else { return }\n\n        let createdFile = self.fileManager.createFile(\n            atPath: newDocumentUrl.path,\n            contents: nil,\n            attributes: [FileAttributeKey.creationDate: Date()]\n        )\n\n        guard createdFile else {\n            print(\"Failed to create new document\")\n            return\n        }\n\n        self.openDocument(withContentsOf: newDocumentUrl, display: true) { _, _, _ in\n            onCompletion()\n        }\n    }\n\n    override func newDocument(_ sender: Any?) {\n        guard let newDocumentUrl = self.newDocumentUrl else { return }\n\n        let createdFile = self.fileManager.createFile(\n            atPath: newDocumentUrl.path,\n            contents: nil,\n            attributes: [FileAttributeKey.creationDate: Date()]\n        )\n        guard createdFile else {\n            print(\"Failed to create new document\")\n            return\n        }\n\n        self.openDocument(withContentsOf: newDocumentUrl, display: true) { _, _, _ in }\n    }\n\n    private var newDocumentUrl: URL? {\n        let panel = NSSavePanel()\n        guard panel.runModal() == .OK else {\n            return nil\n        }\n\n        return panel.url\n    }\n\n    override func openDocument(_ sender: Any?) {\n        self.openDocument(onCompletion: { document, documentWasAlreadyOpen in\n            // TODO: handle errors\n\n            guard let document else {\n                print(\"Failed to unwrap document\")\n                return\n            }\n\n            print(document, documentWasAlreadyOpen)\n        }, onCancel: {})\n    }\n\n    override func openDocument(\n        withContentsOf url: URL,\n        display displayDocument: Bool,\n        completionHandler: @escaping (NSDocument?, Bool, Error?) -> Void\n    ) {\n        guard !openFileInExistingWorkspace(url: url) else {\n            return\n        }\n\n        super.openDocument(withContentsOf: url, display: displayDocument) { document, documentWasAlreadyOpen, error in\n            MainActor.assumeIsolated {\n                if let document {\n                    self.addDocument(document)\n                } else {\n                    let errorMessage = error?.localizedDescription ?? \"unknown error\"\n                    print(\"Unable to open document '\\(url)': \\(errorMessage)\")\n                }\n\n                RecentsStore.documentOpened(at: url)\n                completionHandler(document, documentWasAlreadyOpen, error)\n            }\n        }\n    }\n\n    /// Attempt to open the file URL in an open workspace, finding the nearest workspace to open it in if possible.\n    /// - Parameter url: The file URL to open.\n    /// - Returns: True, if the document was opened in a workspace.\n    private func openFileInExistingWorkspace(url: URL) -> Bool {\n        guard !url.isFolder else { return false }\n        let workspaces = documents.compactMap({ $0 as? WorkspaceDocument })\n\n        // Check open workspaces for the file being opened. Sorted by shared components with the url so we\n        // open the nearest workspace possible.\n        for workspace in workspaces.sorted(by: {\n            ($0.fileURL?.sharedComponents(url) ?? 0) > ($1.fileURL?.sharedComponents(url) ?? 0)\n        }) {\n            // createIfNotFound will still return `nil` if the files don't share a common ancestor.\n            if let newFile = workspace.workspaceFileManager?.getFile(url.absolutePath, createIfNotFound: true) {\n                workspace.editorManager?.openTab(item: newFile)\n                workspace.showWindows()\n                return true\n            }\n        }\n        return false\n    }\n\n    override func removeDocument(_ document: NSDocument) {\n        super.removeDocument(document)\n\n        if let workspace = document as? WorkspaceDocument, let path = workspace.fileURL?.absoluteURL.path() {\n            lspService.closeWorkspace(path)\n        }\n\n        if CodeEditDocumentController.shared.documents.isEmpty {\n            switch Settings[\\.general].reopenWindowAfterClose {\n            case .showWelcomeWindow:\n                // Opens the welcome window\n                openWindow(sceneID: .welcome)\n            case .quit:\n                // Quits CodeEdit\n                NSApplication.shared.terminate(nil)\n            case .doNothing: break\n            }\n        }\n    }\n}\n\nextension NSDocumentController {\n    final func openDocument(onCompletion: @escaping (NSDocument?, Bool) -> Void, onCancel: @escaping () -> Void) {\n        let dialog = NSOpenPanel()\n\n        dialog.title = \"Open Workspace or File\"\n        dialog.showsResizeIndicator = true\n        dialog.showsHiddenFiles = false\n        dialog.canChooseFiles = true\n        dialog.canChooseDirectories = true\n\n        dialog.begin { result in\n            if result ==  NSApplication.ModalResponse.OK, let url = dialog.url {\n                self.openDocument(withContentsOf: url, display: true) { document, documentWasAlreadyOpen, error in\n                    if let error {\n                        NSAlert(error: error).runModal()\n                        return\n                    }\n\n                    guard let document else {\n                        let alert = NSAlert()\n                        alert.messageText = NSLocalizedString(\n                            \"Failed to get document\",\n                            comment: \"Failed to get document\"\n                        )\n                        alert.runModal()\n                        return\n                    }\n                    onCompletion(document, documentWasAlreadyOpen)\n                    print(\"Document:\", document)\n                    print(\"Was already open?\", documentWasAlreadyOpen)\n                }\n            } else if result == NSApplication.ModalResponse.cancel {\n                onCancel()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift",
    "content": "//\n//  CodeEditSplitViewController.swift\n//  CodeEdit\n//\n//  Created by YAPRYNTSEV Aleksey on 31.12.2022.\n//\n\nimport Cocoa\nimport SwiftUI\n\nfinal class CodeEditSplitViewController: NSSplitViewController {\n    static let minSidebarWidth: CGFloat = 242\n    static let maxSnapWidth: CGFloat = snapWidth + 10\n    static let snapWidth: CGFloat = 272\n    static let minSnapWidth: CGFloat = snapWidth - 10\n\n    private weak var workspace: WorkspaceDocument?\n    private weak var navigatorViewModel: NavigatorAreaViewModel?\n    private weak var windowRef: NSWindow?\n    private unowned var hapticPerformer: NSHapticFeedbackPerformer\n\n    // MARK: - Initialization\n\n    init(\n        workspace: WorkspaceDocument,\n        navigatorViewModel: NavigatorAreaViewModel,\n        windowRef: NSWindow,\n        hapticPerformer: NSHapticFeedbackPerformer = NSHapticFeedbackManager.defaultPerformer\n    ) {\n        self.workspace = workspace\n        self.navigatorViewModel = navigatorViewModel\n        self.windowRef = windowRef\n        self.hapticPerformer = hapticPerformer\n        super.init(nibName: nil, bundle: nil)\n    }\n\n    @available(*, unavailable)\n    required init?(coder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n        guard let windowRef else {\n            // swiftlint:disable:next line_length\n            assertionFailure(\"No WindowRef found, not initialized properly or the window was dereferenced and the controller was not.\")\n            return\n        }\n\n        guard let workspace,\n              let navigatorViewModel,\n              let editorManager = workspace.editorManager,\n              let statusBarViewModel = workspace.statusBarViewModel,\n              let utilityAreaModel = workspace.utilityAreaModel,\n              let taskManager = workspace.taskManager else {\n            // swiftlint:disable:next line_length\n            assertionFailure(\"Missing a workspace model: workspace=\\(workspace == nil), navigator=\\(navigatorViewModel == nil), editorManager=\\(workspace?.editorManager == nil), statusBarModel=\\(workspace?.statusBarViewModel == nil), utilityAreaModel=\\(workspace?.utilityAreaModel == nil), taskManager=\\(workspace?.taskManager == nil)\")\n            return\n        }\n\n        splitView.translatesAutoresizingMaskIntoConstraints = false\n\n        let navigator = makeNavigator(view: SettingsInjector {\n            NavigatorAreaView(workspace: workspace, viewModel: navigatorViewModel)\n                .environmentObject(workspace)\n                .environmentObject(editorManager)\n        })\n\n        addSplitViewItem(navigator)\n\n        let workspaceView = SettingsInjector {\n            WindowObserver(window: WindowBox(value: windowRef)) {\n                WorkspaceView()\n                    .environmentObject(workspace)\n                    .environmentObject(editorManager)\n                    .environmentObject(statusBarViewModel)\n                    .environmentObject(utilityAreaModel)\n                    .environmentObject(taskManager)\n                    .environmentObject(workspace.undoRegistration)\n            }\n        }\n\n        let mainContent = NSSplitViewItem(viewController: NSHostingController(rootView: workspaceView))\n        mainContent.titlebarSeparatorStyle = .line\n        mainContent.minimumThickness = 200\n\n        addSplitViewItem(mainContent)\n\n        let inspector = makeInspector(view: SettingsInjector {\n            InspectorAreaView(viewModel: InspectorAreaViewModel())\n                .environmentObject(workspace)\n                .environmentObject(editorManager)\n        })\n\n        addSplitViewItem(inspector)\n    }\n\n    private func makeNavigator(view: some View) -> NSSplitViewItem {\n        let navigator = NSSplitViewItem(sidebarWithViewController: NSHostingController(rootView: view))\n        if #unavailable(macOS 26) {\n            navigator.titlebarSeparatorStyle = .none\n        }\n        navigator.isSpringLoaded = true\n        navigator.minimumThickness = Self.minSidebarWidth\n        navigator.collapseBehavior = .useConstraints\n        return navigator\n    }\n\n    private func makeInspector(view: some View) -> NSSplitViewItem {\n        let inspector = NSSplitViewItem(inspectorWithViewController: NSHostingController(rootView: view))\n        inspector.titlebarSeparatorStyle = .none\n        inspector.minimumThickness = Self.minSidebarWidth\n        inspector.maximumThickness = .greatestFiniteMagnitude\n        inspector.collapseBehavior = .useConstraints\n        inspector.isSpringLoaded = true\n        return inspector\n    }\n\n    override func viewWillAppear() {\n        super.viewWillAppear()\n\n        guard let workspace else { return }\n\n        let navigatorWidth = workspace.getFromWorkspaceState(.splitViewWidth) as? CGFloat\n        splitView.setPosition(navigatorWidth ?? Self.minSidebarWidth, ofDividerAt: 0)\n\n        if let firstSplitView = splitViewItems.first {\n            firstSplitView.isCollapsed = workspace.getFromWorkspaceState(\n                .navigatorCollapsed\n            ) as? Bool ?? false\n        }\n\n        if let lastSplitView = splitViewItems.last {\n            lastSplitView.isCollapsed = workspace.getFromWorkspaceState(\n                .inspectorCollapsed\n            ) as? Bool ?? true\n        }\n\n        workspace.notificationPanel.updateToolbarItem()\n    }\n\n    // MARK: - NSSplitViewDelegate\n\n    /// Perform the spring loaded navigator splits.\n    /// - Note: This could be removed. The only additional functionality this provides over using just the\n    ///         `NSSplitViewItem.isSpringLoaded` & `NSSplitViewItem.minimumThickness` is the haptic feedback we add.\n    /// - Parameters:\n    ///   - splitView: The split view to use.\n    ///   - proposedPosition: The proposed drag position.\n    ///   - dividerIndex: The index of the divider being dragged.\n    /// - Returns: The position to move the divider to.\n    override func splitView(\n        _ splitView: NSSplitView,\n        constrainSplitPosition proposedPosition: CGFloat,\n        ofSubviewAt dividerIndex: Int\n    ) -> CGFloat {\n        switch dividerIndex {\n        case 0:\n            // Navigator\n            if (Self.minSnapWidth...Self.maxSnapWidth).contains(proposedPosition) {\n                return Self.snapWidth\n            } else if proposedPosition <= Self.minSidebarWidth / 2 {\n                hapticCollapse(splitViewItems.first, collapseAction: true)\n                return 0\n            } else {\n                hapticCollapse(splitViewItems.first, collapseAction: false)\n                return max(Self.minSidebarWidth, proposedPosition)\n            }\n        case 1:\n            let proposedWidth = view.frame.width - proposedPosition\n            if proposedWidth <= Self.minSidebarWidth / 2 {\n                hapticCollapse(splitViewItems.last, collapseAction: true)\n                return proposedPosition\n            } else {\n                hapticCollapse(splitViewItems.last, collapseAction: false)\n                return min(view.frame.width - Self.minSidebarWidth, proposedPosition)\n            }\n        default:\n            return proposedPosition\n        }\n    }\n\n    /// Performs a haptic feedback while collapsing or revealing a split item.\n    /// If the item was not previously in the new intended state, a haptic `.alignment` feedback is sent.\n    /// - Parameters:\n    ///   - item: The item to collapse or reveal\n    ///   - collapseAction: Whether or not to collapse the item. Set to true to collapse it.\n    private func hapticCollapse(_ item: NSSplitViewItem?, collapseAction: Bool) {\n        if item?.isCollapsed == !collapseAction {\n            hapticPerformer.perform(.alignment, performanceTime: .now)\n        }\n        item?.isCollapsed = collapseAction\n    }\n\n    /// Save the width of the inspector and navigator between sessions.\n    override func splitViewDidResizeSubviews(_ notification: Notification) {\n        super.splitViewDidResizeSubviews(notification)\n        guard let resizedDivider = notification.userInfo?[\"NSSplitViewDividerIndex\"] as? Int else {\n            return\n        }\n\n        if resizedDivider == 0 {\n            let panel = splitView.subviews[0]\n            let width = panel.frame.size.width\n            if width > 0 {\n                workspace?.addToWorkspaceState(key: .splitViewWidth, value: width)\n            }\n        }\n    }\n\n    func saveNavigatorCollapsedState(isCollapsed: Bool) {\n        workspace?.addToWorkspaceState(key: .navigatorCollapsed, value: isCollapsed)\n    }\n\n    func saveInspectorCollapsedState(isCollapsed: Bool) {\n        workspace?.addToWorkspaceState(key: .inspectorCollapsed, value: isCollapsed)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Panels.swift",
    "content": "//\n//  CodeEditWindowController+Panels.swift\n//  CodeEdit\n//\n//  Created by Simon Kudsk on 11/05/2025.\n//\n\nimport SwiftUI\n\nextension CodeEditWindowController {\n    @objc\n    func objcToggleFirstPanel() {\n        toggleFirstPanel(shouldAnimate: true)\n    }\n\n    /// Toggles the navigator pane, optionally without animation.\n    func toggleFirstPanel(shouldAnimate: Bool = true) {\n        guard let firstSplitView = splitViewController?.splitViewItems.first else { return }\n\n        if shouldAnimate {\n            // Standard animated toggle\n            firstSplitView.animator().isCollapsed.toggle()\n        } else {\n            // Instant toggle (no animation)\n            firstSplitView.isCollapsed.toggle()\n        }\n\n        splitViewController?.saveNavigatorCollapsedState(isCollapsed: firstSplitView.isCollapsed)\n    }\n\n    @objc\n    func objcToggleLastPanel() {\n        toggleLastPanel(shouldAnimate: true)\n    }\n\n    func toggleLastPanel(shouldAnimate: Bool = true) {\n        guard let lastSplitView = splitViewController?.splitViewItems.last else {\n            return\n        }\n\n        if shouldAnimate {\n            // Standard animated toggle\n            NSAnimationContext.runAnimationGroup { _ in\n                lastSplitView.animator().isCollapsed.toggle()\n            }\n        } else {\n            // Instant toggle (no animation)\n            lastSplitView.isCollapsed.toggle()\n        }\n\n        splitViewController?.saveInspectorCollapsedState(isCollapsed: lastSplitView.isCollapsed)\n    }\n\n    // PanelDescriptor, used for an array of panels, for use with \"Hide interface\".\n    private struct PanelDescriptor {\n        /// Returns the current `isCollapsed` value for the panel.\n        let isCollapsed: () -> Bool\n        /// Returns the last stored previous state (or `nil` if none).\n        let getPrevCollapsed: () -> Bool?\n        /// Stores a new previous state (`nil` to clear).\n        let setPrevCollapsed: (Bool?) -> Void\n        /// Performs the actual toggle action for the panel.\n        let toggle: () -> Void\n    }\n\n    // The panels which \"Hide interface\" should interact with.\n    private var panels: [PanelDescriptor] {\n        [\n            PanelDescriptor(\n                isCollapsed: { self.navigatorCollapsed },\n                getPrevCollapsed: { self.prevNavigatorCollapsed },\n                setPrevCollapsed: { self.prevNavigatorCollapsed = $0 },\n                toggle: { self.toggleFirstPanel(shouldAnimate: false) }\n            ),\n            PanelDescriptor(\n                isCollapsed: { self.inspectorCollapsed },\n                getPrevCollapsed: { self.prevInspectorCollapsed },\n                setPrevCollapsed: { self.prevInspectorCollapsed = $0 },\n                toggle: { self.toggleLastPanel(shouldAnimate: false) }\n            ),\n            PanelDescriptor(\n                isCollapsed: { self.workspace?.utilityAreaModel?.isCollapsed ?? true },\n                getPrevCollapsed: { self.prevUtilityAreaCollapsed },\n                setPrevCollapsed: { self.prevUtilityAreaCollapsed = $0 },\n                toggle: { self.workspace?.utilityAreaModel?.togglePanel(animation: false) }\n            ),\n            PanelDescriptor(\n                isCollapsed: { self.toolbarCollapsed },\n                getPrevCollapsed: { self.prevToolbarCollapsed },\n                setPrevCollapsed: { self.prevToolbarCollapsed = $0 },\n                toggle: { self.toggleToolbar() }\n            )\n        ]\n    }\n\n    /// Returns `true` if at least one panel that was visible is still collapsed, meaning the interface is still hidden\n    func isInterfaceStillHidden() -> Bool {\n        // Some panels do not yet have a remembered state\n        if panels.contains(where: { $0.getPrevCollapsed() == nil }) {\n            // Hidden only if all panels are collapsed\n            return panels.allSatisfy { $0.isCollapsed() }\n        }\n\n        // All panels have a remembered state. Check if any that were visible are still collapsed\n        let stillHidden = panels.contains { descriptor in\n            guard let prev = descriptor.getPrevCollapsed() else { return false }\n            return !prev && descriptor.isCollapsed()\n        }\n\n        // If the interface has been restored, reset the remembered states\n        if !stillHidden {\n            DispatchQueue.main.async { [weak self] in\n                self?.resetStoredInterfaceCollapseState()\n            }\n        }\n\n        return stillHidden\n    }\n\n    /// Function for toggling the interface elements on or off\n    ///\n    /// - Parameter shouldHide: Pass `true` to hide all interface panels (and remember their current states),\n    /// or `false` to restore them to how they were before hiding.\n    func toggleInterface(shouldHide: Bool) {\n        // Store the current layout before hiding\n        if shouldHide {\n            storeInterfaceCollapseState()\n        }\n\n        // Iterate over all panels and update their state as needed\n        for panel in panels {\n            let targetState = determineDesiredCollapseState(\n                shouldHide: shouldHide,\n                currentlyCollapsed: panel.isCollapsed(),\n                previouslyCollapsed: panel.getPrevCollapsed()\n            )\n            if panel.isCollapsed() != targetState {\n                panel.toggle()\n            }\n        }\n    }\n\n    /// Calculates the collapse state an interface element should have after a hide / show toggle.\n    /// - Parameters:\n    ///   - shouldHide: `true` when we’re hiding the whole interface.\n    ///   - currentlyCollapsed: The panels current state\n    ///   - previouslyCollapsed: The state we saved the last time we hid the UI, if any.\n    /// - Returns: `true` for visible element, `false` for collapsed element\n    func determineDesiredCollapseState(shouldHide: Bool, currentlyCollapsed: Bool, previouslyCollapsed: Bool?) -> Bool {\n        // If ShouldHide, everything should close\n        if shouldHide {\n            return true\n        }\n\n        // If not hiding, and not currently collapsed, the panel should remain as such.\n        if !currentlyCollapsed {\n            return false\n        }\n\n        // If the panel is currently collapsed and we are \"showing\" or \"restoring\":\n        // Option 1: Restore to its previously remembered state if available.\n        // Option 2: If no previously remembered state, default to making it visible (not collapsed).\n        return previouslyCollapsed ?? false\n    }\n\n    /// Function for storing the current interface visibility states\n    func storeInterfaceCollapseState() {\n        for panel in panels {\n            panel.setPrevCollapsed(panel.isCollapsed())\n        }\n    }\n\n    /// Function for resetting the stored interface visibility states\n    func resetStoredInterfaceCollapseState() {\n        for panel in panels {\n            panel.setPrevCollapsed(nil)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift",
    "content": "//\n//  CodeEditWindowController+Toolbar.swift\n//  CodeEdit\n//\n//  Created by Daniel Zhu on 5/10/24.\n//\n\nimport AppKit\nimport SwiftUI\nimport Combine\n\nextension CodeEditWindowController {\n    internal func setupToolbar() {\n        let toolbar = NSToolbar(identifier: UUID().uuidString)\n        toolbar.delegate = self\n        toolbar.showsBaselineSeparator = false\n        self.window?.titleVisibility = toolbarCollapsed ? .visible : .hidden\n        if #available(macOS 26, *) {\n            self.window?.toolbarStyle = .automatic\n            toolbar.centeredItemIdentifiers = [.activityViewer, .notificationItem]\n            toolbar.displayMode = .iconOnly\n            self.window?.titlebarAppearsTransparent = true\n        } else {\n            self.window?.toolbarStyle = .unifiedCompact\n            toolbar.displayMode = .labelOnly\n        }\n        self.window?.titlebarSeparatorStyle = .automatic\n        self.window?.toolbar = toolbar\n    }\n\n    func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {\n        var items: [NSToolbarItem.Identifier] = [\n            .toggleFirstSidebarItem,\n            .flexibleSpace,\n        ]\n\n        if #available(macOS 26, *) {\n            items += [.taskSidebarItem]\n        } else {\n            items += [\n                .stopTaskSidebarItem,\n                .startTaskSidebarItem,\n            ]\n        }\n\n        items += [\n            .sidebarTrackingSeparator,\n            .branchPicker,\n            .flexibleSpace,\n        ]\n\n        if #available(macOS 26, *) {\n            items += [\n                .activityViewer,\n                .space,\n                .notificationItem,\n            ]\n        } else {\n            items += [\n                .activityViewer,\n                .notificationItem,\n                .flexibleSpace,\n            ]\n        }\n\n        items += [\n            .flexibleSpace,\n            .itemListTrackingSeparator,\n            .flexibleSpace,\n            .toggleLastSidebarItem\n        ]\n\n        return items\n    }\n\n    func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {\n        var items: [NSToolbarItem.Identifier] = [\n            .toggleFirstSidebarItem,\n            .sidebarTrackingSeparator,\n            .flexibleSpace,\n            .itemListTrackingSeparator,\n            .toggleLastSidebarItem,\n            .branchPicker,\n            .activityViewer,\n            .notificationItem,\n        ]\n\n        if #available(macOS 26, *) {\n            items += [\n                .taskSidebarItem\n            ]\n        } else {\n            items += [\n                .startTaskSidebarItem,\n                .stopTaskSidebarItem\n            ]\n        }\n\n        return items\n    }\n\n    func toggleToolbar() {\n        toolbarCollapsed.toggle()\n        workspace?.addToWorkspaceState(key: .toolbarCollapsed, value: toolbarCollapsed)\n        updateToolbarVisibility()\n    }\n\n    func updateToolbarVisibility() {\n        if toolbarCollapsed {\n            window?.titleVisibility = .visible\n            window?.title = workspace?.workspaceFileManager?.folderUrl.lastPathComponent ?? \"Empty\"\n            window?.toolbar = nil\n        } else {\n            window?.titleVisibility = .hidden\n            setupToolbar()\n        }\n    }\n\n    // swiftlint:disable:next function_body_length cyclomatic_complexity\n    func toolbar(\n        _ toolbar: NSToolbar,\n        itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier,\n        willBeInsertedIntoToolbar flag: Bool\n    ) -> NSToolbarItem? {\n        switch itemIdentifier {\n        case .itemListTrackingSeparator:\n            guard let splitViewController else { return nil }\n\n            return NSTrackingSeparatorToolbarItem(\n                identifier: .itemListTrackingSeparator,\n                splitView: splitViewController.splitView,\n                dividerIndex: 1\n            )\n        case .toggleFirstSidebarItem:\n            let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.toggleFirstSidebarItem)\n            toolbarItem.paletteLabel = \" Navigator Sidebar\"\n            toolbarItem.toolTip = \"Hide or show the Navigator\"\n            toolbarItem.isBordered = true\n            toolbarItem.target = self\n            toolbarItem.action = #selector(self.objcToggleFirstPanel)\n            toolbarItem.image = NSImage(\n                systemSymbolName: \"sidebar.leading\",\n                accessibilityDescription: nil\n            )?.withSymbolConfiguration(.init(scale: .large))\n\n            return toolbarItem\n        case .toggleLastSidebarItem:\n            let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.toggleLastSidebarItem)\n            toolbarItem.paletteLabel = \"Inspector Sidebar\"\n            toolbarItem.toolTip = \"Hide or show the Inspectors\"\n            toolbarItem.isBordered = true\n            toolbarItem.target = self\n            toolbarItem.action = #selector(self.objcToggleLastPanel)\n            toolbarItem.image = NSImage(\n                systemSymbolName: \"sidebar.trailing\",\n                accessibilityDescription: nil\n            )?.withSymbolConfiguration(.init(scale: .large))\n\n            return toolbarItem\n        case .stopTaskSidebarItem:\n            return stopTaskSidebarItem()\n        case .startTaskSidebarItem:\n            return startTaskSidebarItem()\n        case .branchPicker:\n            let toolbarItem = NSToolbarItem(itemIdentifier: .branchPicker)\n            let view = NSHostingView(\n                rootView: ToolbarBranchPicker(\n                    workspaceFileManager: workspace?.workspaceFileManager\n                )\n            )\n            toolbarItem.view = view\n            toolbarItem.isBordered = false\n            return toolbarItem\n        case .activityViewer:\n            return activityViewerItem()\n        case .notificationItem:\n            return notificationItem()\n        case .taskSidebarItem:\n            guard #available(macOS 26, *) else {\n                fatalError(\"Unified task sidebar item used on pre-tahoe platform.\")\n            }\n            guard let workspace,\n                    let stop = StopTaskToolbarItem(workspace: workspace) else {\n                return nil\n            }\n            let start = StartTaskToolbarItem(workspace: workspace)\n\n            let group = NSToolbarItemGroup(itemIdentifier: .taskSidebarItem)\n            group.isBordered = true\n            group.controlRepresentation = .expanded\n            group.selectionMode = .momentary\n            group.subitems = [stop, start]\n\n            return group\n        default:\n            return NSToolbarItem(itemIdentifier: itemIdentifier)\n        }\n    }\n\n    private func stopTaskSidebarItem() -> NSToolbarItem? {\n        let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.stopTaskSidebarItem)\n\n        guard let taskManager = workspace?.taskManager else { return nil }\n\n        let view = NSHostingView(\n            rootView: StopTaskToolbarButton(taskManager: taskManager)\n        )\n        toolbarItem.view = view\n\n        return toolbarItem\n    }\n\n    private func startTaskSidebarItem() -> NSToolbarItem? {\n        let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.startTaskSidebarItem)\n\n        guard let taskManager = workspace?.taskManager else { return nil }\n        guard let workspace = workspace else { return nil }\n\n        let view = NSHostingView(\n            rootView: StartTaskToolbarButton(taskManager: taskManager)\n                .environmentObject(workspace)\n        )\n        toolbarItem.view = view\n\n        return toolbarItem\n    }\n\n    private func notificationItem() -> NSToolbarItem? {\n        let toolbarItem = NSToolbarItem(itemIdentifier: .notificationItem)\n        guard let workspace = workspace else { return nil }\n        let view = NSHostingView(rootView: NotificationToolbarItem().environmentObject(workspace))\n        toolbarItem.view = view\n        return toolbarItem\n    }\n\n    private func activityViewerItem() -> NSToolbarItem? {\n        let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.activityViewer)\n        toolbarItem.visibilityPriority = .user\n        guard let workspaceSettingsManager = workspace?.workspaceSettingsManager,\n              let taskNotificationHandler = workspace?.taskNotificationHandler,\n              let taskManager = workspace?.taskManager\n        else { return nil }\n\n        let view = NSHostingView(\n            rootView: ActivityViewer(\n                workspaceFileManager: workspace?.workspaceFileManager,\n                workspaceSettingsManager: workspaceSettingsManager,\n                taskNotificationHandler: taskNotificationHandler,\n                taskManager: taskManager\n            )\n        )\n\n        let weakWidth = view.widthAnchor.constraint(equalToConstant: 650)\n        weakWidth.priority = .defaultLow\n        let strongWidth = view.widthAnchor.constraint(greaterThanOrEqualToConstant: 200)\n        strongWidth.priority = .defaultHigh\n\n        NSLayoutConstraint.activate([\n            weakWidth,\n            strongWidth\n        ])\n\n        toolbarItem.view = view\n        return toolbarItem\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift",
    "content": "//\n//  CodeEditWindowController.swift\n//  CodeEdit\n//\n//  Created by Pavel Kasila on 18.03.22.\n//\n\nimport Cocoa\nimport SwiftUI\nimport Combine\n\nfinal class CodeEditWindowController: NSWindowController, NSToolbarDelegate, ObservableObject, NSWindowDelegate {\n    @Published var navigatorCollapsed: Bool = false\n    @Published var inspectorCollapsed: Bool = false\n    @Published var toolbarCollapsed: Bool = false\n\n    // These variables store the state of the windows when using \"Hide interface\"\n    @Published var prevNavigatorCollapsed: Bool?\n    @Published var prevInspectorCollapsed: Bool?\n    @Published var prevUtilityAreaCollapsed: Bool?\n    @Published var prevToolbarCollapsed: Bool?\n\n    private var panelOpen = false\n\n    var observers: [NSKeyValueObservation] = []\n\n    var workspace: WorkspaceDocument?\n    var workspaceSettingsWindow: NSWindow?\n    var quickOpenPanel: SearchPanel?\n    var commandPalettePanel: SearchPanel?\n    var navigatorSidebarViewModel: NavigatorAreaViewModel?\n\n    internal var cancellables = [AnyCancellable]()\n\n    var splitViewController: CodeEditSplitViewController? {\n        contentViewController as? CodeEditSplitViewController\n    }\n\n    init(\n        window: NSWindow?,\n        workspace: WorkspaceDocument?\n    ) {\n        super.init(window: window)\n        window?.delegate = self\n        guard let workspace else { return }\n        self.workspace = workspace\n        self.toolbarCollapsed = workspace.getFromWorkspaceState(.toolbarCollapsed) as? Bool ?? false\n        guard let splitViewController = setupSplitView(with: workspace) else {\n            fatalError(\"Failed to set up content view.\")\n        }\n\n        // Previous:\n        // An NSHostingController is used, so the root viewController of the window is a SwiftUI-managed one.\n        // This allows us to use some SwiftUI features, like focusedSceneObject.\n        // -----\n        // let view = CodeEditSplitView(controller: splitViewController).ignoresSafeArea()\n        // contentViewController = NSHostingController(rootView: view)\n        // -----\n        //\n        // New:\n        // The previous decision led to a very jank split controller mechanism because SwiftUI's layout system is not\n        // very compatible with AppKit's when it comes to the inspector/navigator toolbar & split view system.\n        // -----\n        contentViewController = splitViewController\n        // -----\n\n        observers = [\n            splitViewController.splitViewItems.first!.observe(\\.isCollapsed, changeHandler: { [weak self] item, _ in\n                self?.navigatorCollapsed = item.isCollapsed\n            }),\n            splitViewController.splitViewItems.last!.observe(\\.isCollapsed, changeHandler: { [weak self] item, _ in\n                self?.inspectorCollapsed = item.isCollapsed\n            })\n        ]\n\n        setupToolbar()\n        updateToolbarVisibility()\n        registerCommands()\n    }\n\n    deinit {\n        cancellables.forEach({ $0.cancel() })\n        cancellables.removeAll()\n    }\n\n    @available(*, unavailable)\n    required init?(coder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n    private func setupSplitView(with workspace: WorkspaceDocument) -> CodeEditSplitViewController? {\n        guard let window else {\n            assertionFailure(\"No window found for this controller. Cannot set up content.\")\n            return nil\n        }\n\n        let navigatorModel = NavigatorAreaViewModel()\n        navigatorSidebarViewModel = navigatorModel\n        self.listenToDocumentEdited(workspace: workspace)\n        return CodeEditSplitViewController(\n            workspace: workspace,\n            navigatorViewModel: navigatorModel,\n            windowRef: window\n        )\n    }\n\n    private func getSelectedCodeFile() -> CodeFileDocument? {\n        workspace?.editorManager?.activeEditor.selectedTab?.file.fileDocument\n    }\n\n    @IBAction func saveDocument(_ sender: Any) {\n        guard let codeFile = getSelectedCodeFile() else { return }\n        codeFile.save(sender)\n        workspace?.editorManager?.activeEditor.temporaryTab = nil\n    }\n\n    @IBAction func openCommandPalette(_ sender: Any) {\n        if let workspace, let state = workspace.commandsPaletteState {\n            if let commandPalettePanel {\n                if commandPalettePanel.isKeyWindow {\n                    commandPalettePanel.close()\n                    self.panelOpen = false\n                    state.reset()\n                    return\n                } else {\n                    state.reset()\n                    window?.addChildWindow(commandPalettePanel, ordered: .above)\n                    commandPalettePanel.makeKeyAndOrderFront(self)\n                    self.panelOpen = true\n                }\n            } else {\n                let panel = SearchPanel()\n                self.commandPalettePanel = panel\n                let contentView = QuickActionsView(state: state) {\n                    panel.close()\n                    self.panelOpen = false\n                }\n                panel.contentView = NSHostingView(rootView: SettingsInjector { contentView })\n                window?.addChildWindow(panel, ordered: .above)\n                panel.makeKeyAndOrderFront(self)\n                self.panelOpen = true\n            }\n        }\n    }\n\n    /// Opens the search navigator and focuses the search field\n    @IBAction func openSearchNavigator(_ sender: Any? = nil) {\n        if navigatorCollapsed {\n            toggleFirstPanel()\n        }\n\n        if let navigatorViewModel = navigatorSidebarViewModel,\n           let searchTab = navigatorViewModel.tabItems.first(where: { $0 == .search }) {\n            DispatchQueue.main.async {\n                self.workspace?.searchState?.shouldFocusSearchField = true\n                navigatorViewModel.setNavigatorTab(tab: searchTab)\n            }\n        }\n    }\n\n    @IBAction func openQuickly(_ sender: Any?) {\n        if let workspace, let state = workspace.openQuicklyViewModel {\n            if let quickOpenPanel {\n                if quickOpenPanel.isKeyWindow {\n                    quickOpenPanel.close()\n                    self.panelOpen = false\n                    return\n                } else {\n                    window?.addChildWindow(quickOpenPanel, ordered: .above)\n                    quickOpenPanel.makeKeyAndOrderFront(self)\n                    self.panelOpen = true\n                }\n            } else {\n                let panel = SearchPanel()\n                self.quickOpenPanel = panel\n\n                let contentView = OpenQuicklyView(state: state) {\n                    panel.close()\n                    self.panelOpen = false\n                } openFile: { file in\n                    workspace.editorManager?.openTab(item: file)\n                }.environmentObject(workspace)\n\n                panel.contentView = NSHostingView(rootView: SettingsInjector { contentView })\n                window?.addChildWindow(panel, ordered: .above)\n                panel.makeKeyAndOrderFront(self)\n                self.panelOpen = true\n            }\n        }\n    }\n\n    @IBAction func closeCurrentTab(_ sender: Any) {\n        if self.panelOpen { return }\n        if (workspace?.editorManager?.activeEditor.tabs ?? []).isEmpty {\n            self.closeActiveEditor(self)\n        } else {\n            workspace?.editorManager?.activeEditor.closeSelectedTab()\n        }\n    }\n\n    @IBAction func closeActiveEditor(_ sender: Any) {\n        if workspace?.editorManager?.editorLayout.findSomeEditor(\n            except: workspace?.editorManager?.activeEditor\n        ) == nil {\n            NSApp.sendAction(#selector(NSWindow.performClose(_:)), to: NSApp.keyWindow, from: nil)\n        } else {\n            workspace?.editorManager?.activeEditor.close()\n        }\n    }\n\n    func windowShouldClose(_ sender: NSWindow) -> Bool {\n        cancellables.forEach({ $0.cancel() })\n        cancellables.removeAll()\n\n        for _ in 0..<(splitViewController?.children.count ?? 0) {\n            splitViewController?.removeChild(at: 0)\n        }\n        contentViewController?.removeFromParent()\n        contentViewController = nil\n\n        workspaceSettingsWindow?.close()\n        workspaceSettingsWindow = nil\n        quickOpenPanel = nil\n        commandPalettePanel = nil\n        navigatorSidebarViewModel = nil\n        workspace = nil\n        return true\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift",
    "content": "//\n//  CodeEditWindowControllerExtensions.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 10/14/23.\n//\n\nimport SwiftUI\nimport Combine\n\nextension CodeEditWindowController {\n    /// These are example items that added as commands to command palette\n    func registerCommands() {\n        CommandManager.shared.addCommand(\n            name: \"Quick Open\",\n            title: \"Quick Open\",\n            id: \"quick_open\",\n            command: { [weak self] in self?.openQuickly(nil) }\n        )\n\n        CommandManager.shared.addCommand(\n            name: \"Toggle Navigator\",\n            title: \"Toggle Navigator\",\n            id: \"toggle_left_sidebar\",\n            command: { [weak self] in self?.toggleFirstPanel() }\n        )\n\n        CommandManager.shared.addCommand(\n            name: \"Toggle Inspector\",\n            title: \"Toggle Inspector\",\n            id: \"toggle_right_sidebar\",\n            command: { [weak self] in self?.toggleLastPanel() }\n        )\n    }\n\n    // Listen to changes in all tabs/files\n    internal func listenToDocumentEdited(workspace: WorkspaceDocument) {\n        workspace.editorManager?.$activeEditor\n            .flatMap({ editor in\n                editor.$tabs\n            })\n            .compactMap({ tab in\n                Publishers.MergeMany(tab.elements.compactMap({ $0.file.fileDocumentPublisher }))\n            })\n            .switchToLatest()\n            .compactMap({ fileDocument in\n                fileDocument?.isDocumentEditedPublisher\n            })\n            .flatMap({ $0 })\n            .sink { isDocumentEdited in\n                if isDocumentEdited {\n                    self.setDocumentEdited(true)\n                    return\n                }\n\n                self.updateDocumentEdited(workspace: workspace)\n            }\n            .store(in: &cancellables)\n\n        // Listen to change of tabs, if closed tab without saving content,\n        // we also need to recalculate isDocumentEdited\n        workspace.editorManager?.$activeEditor\n            .flatMap({ editor in\n                editor.$tabs\n            })\n            .sink { _ in\n                self.updateDocumentEdited(workspace: workspace)\n            }\n            .store(in: &cancellables)\n    }\n\n    // Recalculate documentEdited by checking if any tab/file is edited\n    private func updateDocumentEdited(workspace: WorkspaceDocument) {\n        let hasEditedDocuments = !(workspace\n            .editorManager?\n            .editorLayout\n            .gatherOpenFiles()\n            .filter({ $0.fileDocument?.isDocumentEdited == true })\n            .isEmpty ?? true)\n        self.setDocumentEdited(hasEditedDocuments)\n    }\n\n    @IBAction func openWorkspaceSettings(_ sender: Any) {\n        guard let window = window,\n              let workspace = workspace,\n              let workspaceSettingsManager = workspace.workspaceSettingsManager,\n              let taskManager = workspace.taskManager\n        else { return }\n\n        if let workspaceSettingsWindow, workspaceSettingsWindow.isVisible {\n            workspaceSettingsWindow.makeKeyAndOrderFront(self)\n        } else {\n            let settingsWindow = NSWindow()\n            self.workspaceSettingsWindow = settingsWindow\n            let contentView = CEWorkspaceSettingsView(\n                dismiss: { [weak self, weak settingsWindow] in\n                    guard let settingsWindow else { return }\n                    self?.window?.endSheet(settingsWindow)\n                 }\n            )\n            .environmentObject(workspaceSettingsManager)\n            .environmentObject(workspace)\n            .environmentObject(taskManager)\n\n            settingsWindow.contentView = NSHostingView(rootView: contentView)\n            settingsWindow.titlebarAppearsTransparent = true\n            settingsWindow.setContentSize(NSSize(width: 515, height: 515))\n            settingsWindow.setAccessibilityTitle(\"Workspace Settings\")\n\n            window.beginSheet(settingsWindow, completionHandler: nil)\n        }\n    }\n}\n\nextension NSToolbarItem.Identifier {\n    static let toggleFirstSidebarItem: NSToolbarItem.Identifier = NSToolbarItem.Identifier(\"ToggleFirstSidebarItem\")\n    static let toggleLastSidebarItem: NSToolbarItem.Identifier = NSToolbarItem.Identifier(\"ToggleLastSidebarItem\")\n    static let stopTaskSidebarItem: NSToolbarItem.Identifier = NSToolbarItem.Identifier(\"StopTaskSidebarItem\")\n    static let startTaskSidebarItem: NSToolbarItem.Identifier = NSToolbarItem.Identifier(\"StartTaskSidebarItem\")\n    static let itemListTrackingSeparator = NSToolbarItem.Identifier(\"ItemListTrackingSeparator\")\n    static let branchPicker: NSToolbarItem.Identifier = NSToolbarItem.Identifier(\"BranchPicker\")\n    static let activityViewer: NSToolbarItem.Identifier = NSToolbarItem.Identifier(\"ActivityViewer\")\n    static let notificationItem = NSToolbarItem.Identifier(\"notificationItem\")\n\n    static let taskSidebarItem: NSToolbarItem.Identifier = NSToolbarItem.Identifier(\"TaskSidebarItem\")\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/Indexer/AsyncFileIterator.swift",
    "content": "//\n//  AsyncFileIterator.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 12/4/23.\n//\n\nimport Foundation\n\n/// Given a list of file URLs, asynchronously fetches their contents and returns them iteratively.\n/// Returns files as a ``SearchIndexer/AsyncManager/TextFile`` struct, used to index workspaces.\nstruct AsyncFileIterator: AsyncSequence, AsyncIteratorProtocol {\n    typealias TextFile = SearchIndexer.AsyncManager.TextFile\n    typealias Element = (TextFile, Int)\n\n    let fileURLs: [URL]\n    var currentIdx = 0\n\n    mutating func next() async -> Element? {\n        guard !Task.isCancelled else {\n            return nil\n        }\n\n        defer {\n            currentIdx += 1\n        }\n\n        // Loop until we either find a loadable file or run out of URLs\n        var foundContent: TextFile?\n        while foundContent == nil {\n            guard currentIdx < fileURLs.count else {\n                return nil\n            }\n\n            let fileURL = fileURLs[currentIdx]\n            if let content = try? String(contentsOf: fileURL) {\n                foundContent = TextFile(url: fileURL.standardizedFileURL, text: content)\n            } else {\n                currentIdx += 1\n            }\n        }\n        return (foundContent!, currentIdx)\n    }\n\n    func makeAsyncIterator() -> AsyncFileIterator {\n        self\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/Indexer/FileHelper.swift",
    "content": "//\n//  FileHelper.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 18.11.23.\n//\n\nimport Foundation\n\nenum FileHelper {\n    static func urlIsFolder(_ url: URL) -> Bool {\n        var isDirectory: ObjCBool = false\n        let exists = FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory)\n        return exists && isDirectory.boolValue\n    }\n\n    static func urlIsFile(_ url: URL) -> Bool {\n        var isDirectory: ObjCBool = false\n        let exists = FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory)\n        return exists && !isDirectory.boolValue\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/Indexer/SearchIndexer+Add.swift",
    "content": "//\n//  SearchIndexer+Add.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 18.11.23.\n//\n\nimport Foundation\n\nextension SearchIndexer {\n    /// Add some text to the index for a given URL\n    ///\n    /// - Parameters:\n    ///   - url: The identifying URL for the text\n    ///   - text: The text to add\n    ///   - canReplace: if true, can attempt to replace an existing document with the new one.\n    /// - Returns: true if the text was successfully added to the index, false otherwise\n    public func addFileWithText(_ url: URL, text: String, canReplace: Bool = true) -> Bool {\n        guard let index = self.index,\n              let document = SKDocumentCreateWithURL(url as CFURL) else {\n            return false\n        }\n\n        return modifyIndexQueue.sync {\n            SKIndexAddDocumentWithText(index, document.takeRetainedValue(), text as CFString, canReplace)\n        }\n    }\n\n    /// Adds text content to the indexer using a URL string.\n    ///\n    /// - Parameters:\n    ///   - textURL: A string representing the URL of the text content.\n    ///   - text: The text content to be added to the indexer.\n    ///   - canReplace: If true, can attempt to replace an existing document with the new one. Defaults to `true`.\n    ///\n    /// - Returns: `true` if the text content is successfully added to the indexer; otherwise, returns `false`.\n    public func addFileWithText(textURL: String, text: String, canReplace: Bool = true) -> Bool {\n        guard let url = URL(string: textURL) else {\n            return false\n        }\n        return self.addFileWithText(url, text: text, canReplace: canReplace)\n    }\n\n    /// Adds a file as a document to the index.\n    ///\n    /// - Parameters:\n    ///   - fileURL: The file URL for the document, e.g., file:///User/Essay.txt.\n    ///   - mimeType: \n    ///         An optional MIME type. If nil, the function attempts to determine the file type from the extension.\n    ///   - canReplace:\n    ///         A flag indicating whether to attempt to replace an existing document with the new one. \n    ///         Defaults to `true`.\n    ///\n    /// - Returns: `true` if the command was successful. Even if the document wasn't updated, it still returns `true`.\n    ///\n    /// - Important:\n    ///   If the document wasn't updated, the function still returns `true`. \n    ///   Be cautious when relying solely on the return value to determine if the document was replaced.\n    public func addFile(fileURL: URL, mimeType: String? = nil, canReplace: Bool = true) -> Bool {\n        guard self.dataExtractorLoaded,\n              let index = self.index,\n              let document = SKDocumentCreateWithURL(fileURL as CFURL) else {\n            return false\n        }\n        // Try to detect the mime type if it wasn't specified\n        let mime = mimeType ?? self.detectMimeType(fileURL)\n\n        return modifyIndexQueue.sync {\n            SKIndexAddDocument(index, document.takeRetainedValue(), mime as CFString?, canReplace)\n        }\n    }\n\n    /// Recursively adds the files contained within a folder to the search index.\n    ///\n    /// - Parameters:\n    ///   - folderURL: The folder to be indexed.\n    ///   - canReplace: \n    ///         A flag indicating whether existing documents within the index can be replaced. Defaults to `true`.\n    ///\n    /// - Returns: The URLs of documents added to the index. If `folderURL` isn't a folder, returns an empty array.\n    public func addFolderContent(folderURL: URL, canReplace: Bool = true) -> [URL] {\n        let fileManger = FileManager.default\n\n        var isDir: ObjCBool = false\n        guard fileManger.fileExists(atPath: folderURL.path, isDirectory: &isDir),\n              isDir.boolValue == true else {\n            return []\n        }\n\n        var addedUrls: [URL] = []\n        let enumerator = fileManger.enumerator(at: folderURL, includingPropertiesForKeys: nil)\n        while let fileURL = enumerator?.nextObject() as? URL {\n            if fileManger.fileExists(atPath: fileURL.path, isDirectory: &isDir),\n               isDir.boolValue == false,\n               self.addFile(fileURL: fileURL, canReplace: canReplace) {\n                addedUrls.append(fileURL)\n            }\n        }\n\n        return addedUrls\n    }\n\n    /// Removes a document from the index.\n    ///\n    /// - Parameter url: The identifying URL for the document.\n    ///\n    /// - Returns: `true` if the document was successfully removed, `false` otherwise. \n    /// **Note:** If the document didn't exist, this also returns `true`.\n    public func removeDocument(url: URL) -> Bool {\n        let document = SKDocumentCreateWithURL(url as CFURL).takeRetainedValue()\n        return self.remove(document: document)\n    }\n\n    /// Remove an array of documents from the index\n    ///\n    /// - Parameter urls: An array of URLs identifying the documents to be removed.\n    public func removeDocuments(urls: [URL]) {\n        urls.forEach { url in\n            _ = self.removeDocument(url: url)\n        }\n    }\n\n    /// Retrieves the indexing state of a document at the specified URL.\n    ///\n    /// - Parameter url: The URL of the document.\n    ///\n    /// - Returns:\n    ///     The indexing state of the document. Returns `kSKDocumentStateNotIndexed` if the document is not indexed.\n    public func documentState(_ url: URL) -> SKDocumentIndexState {\n        if let index = self.index,\n           let document = SKDocumentCreateWithURL(url as CFURL) {\n            return SKIndexGetDocumentState(index, document.takeUnretainedValue())\n        }\n        return kSKDocumentStateNotIndexed\n    }\n\n    /// Checks if a document at the specified URL is indexed.\n    ///\n    /// - Parameter url: The URL of the document.\n    ///\n    /// - Returns: `true` if the document is indexed; otherwise, returns `false`.\n    public func documentIndexed(_ url: URL) -> Bool {\n        return self.documentState(url) == kSKDocumentStateIndexed\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/Indexer/SearchIndexer+AsyncController.swift",
    "content": "//\n//  SearchIndexer+AsyncController.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 18.11.23.\n//\n\nimport Foundation\n\nextension SearchIndexer {\n    /// Manager for SearchIndexer object that supports async calls to the index\n    class AsyncManager {\n        /// An instance of the SearchIndexer\n        let index: SearchIndexer\n        private let addQueue = DispatchQueue(label: \"app.codeedit.CodeEdit.AddFilesToIndex\", attributes: .concurrent)\n        private let searchQueue = DispatchQueue(label: \"app.codeedit.CodeEdit.SearchIndex\", attributes: .concurrent)\n\n        init(index: SearchIndexer) {\n            self.index = index\n        }\n\n        class TextFile {\n            let url: URL\n            let text: String\n\n            /// Create a text async task\n            ///\n            /// - Parameters:\n            ///   - url: the identifying document URL\n            ///   - text: The text to add to the index\n            init(url: URL, text: String) {\n                self.url = url\n                self.text = text\n            }\n        }\n\n        // MARK: - Search\n\n        /// Performs an asynchronous progressive search on the index for the specified query.\n        ///\n        /// - Parameters:\n        ///   - query: The search query string.\n        ///   - maxResults: The maximum number of results to retrieve in each chunk.\n        ///   - timeout: The timeout duration for each search operation. Default is 1.0 second.\n        ///\n        /// - Returns: An asynchronous stream (`AsyncStream`) of search results in chunks.\n        /// The search results are returned in the form of a `SearchIndexer.ProgressiveSearch.Results` object.\n        ///\n        /// This function initiates a progressive search on the index for the specified query\n        /// and asynchronously yields search results in chunks using an `AsyncStream`.\n        /// The search continues until there are no more results or the specified timeout is reached.\n        ///\n        /// - Warning: Prior to calling this function,\n        /// ensure that the `index` has been flushed to search within the most up-to-date data.\n        ///\n        /// Example usage:\n        /// ```swift\n        /// let searchStream = await asyncController.search(query: searchQuery, 20)\n        /// for try await result in searchStream {\n        ///     // Process each result\n        ///     print(result)\n        /// }\n        /// ```\n        func search(\n            query: String,\n            _ maxResults: Int,\n            timeout: TimeInterval = 1.0\n        ) async -> AsyncStream<SearchIndexer.ProgressiveSearch.Results> {\n            let search = index.progressiveSearch(query: query)\n\n            return AsyncStream { configuration in\n                var moreResultsAvailable = true\n                while moreResultsAvailable && !Task.isCancelled {\n                    let results = search.getNextSearchResultsChunk(limit: maxResults, timeout: timeout)\n                    moreResultsAvailable = results.moreResultsAvailable\n                    configuration.yield(results)\n                }\n                configuration.finish()\n            }\n        }\n\n        // MARK: - Add\n\n        /// Adds files from an array of TextFile objects to the index asynchronously.\n        ///\n        /// - Parameters:\n        ///   - files: An array of TextFile objects containing the information about the files to be added.\n        ///   - flushWhenComplete: A boolean flag indicating whether to flush \n        ///   the index when the operation is complete. Default is `false`.\n        ///\n        /// - Returns: An array of booleans indicating the success of adding each file to the index.\n        func addText(\n            files: [TextFile],\n            flushWhenComplete: Bool = false\n        ) async -> [Bool] {\n\n            var addedFiles = [Bool]()\n\n            // Asynchronously iterate through the provided files using a task group\n            await withTaskGroup(of: Bool.self) { taskGroup in\n                for file in files {\n                    taskGroup.addTask {\n                        // Add the file to the index and return the success status\n                        return self.index.addFileWithText(file.url, text: file.text, canReplace: true)\n                    }\n                }\n\n                for await result in taskGroup {\n                    addedFiles.append(result)\n                }\n            }\n\n            if flushWhenComplete {\n                index.flush()\n            }\n\n            return addedFiles\n        }\n\n        /// Adds files from an array of URLs to the index asynchronously.\n        ///\n        /// - Parameters:\n        ///   - urls: An array of URLs representing the file locations to be added to the index.\n        ///   - flushWhenComplete: A boolean flag indicating whether to flush\n        ///   the index when the operation is complete. Default is `false`.\n        ///\n        /// - Returns: An array of booleans indicating the success of adding each file to the index.\n        /// - Warning: Prefer using `addText` when possible as SearchKit does not have the ability\n        ///  to read every file type. For example, it is often not possible to read Swift files.\n        func addFiles(\n            urls: [URL],\n            flushWhenComplete: Bool = false\n        ) async -> [Bool] {\n            var addedURLs = [Bool]()\n\n            await withTaskGroup(of: Bool.self) { taskGroup in\n                for url in urls {\n                    taskGroup.addTask {\n                        return self.index.addFile(fileURL: url, canReplace: true)\n                    }\n                }\n\n                for await results in taskGroup {\n                    addedURLs.append(results)\n                }\n            }\n\n            return addedURLs\n        }\n\n        /// Adds files from a folder specified by the given URL to the index asynchronously.\n        ///\n        /// - Parameters:\n        ///   - url: The URL of the folder containing files to be added to the index.\n        ///   - flushWhenComplete: A boolean flag indicating whether to flush \n        ///   the index when the operation is complete. Default is `false`.\n        ///\n        /// This function uses asynchronous processing to add files from the specified folder to the index.\n        ///\n        /// - Note: Subfolders within the specified folder are also processed.\n        func addFolder(\n            url: URL,\n            flushWhenComplete: Bool = false\n        ) {\n            let dispatchGroup = DispatchGroup()\n\n            let fileManager = FileManager.default\n            let enumerator = fileManager.enumerator(\n                at: url,\n                includingPropertiesForKeys: [.isRegularFileKey],\n                options: [.skipsHiddenFiles],\n                errorHandler: nil\n            )!\n\n            for case let fileURL as URL in enumerator {\n                dispatchGroup.enter()\n\n                if FileHelper.urlIsFolder(url) {\n                    addQueue.async { [weak self] in\n                        guard let self = self else { return }\n                        self.addFolder(url: url)\n                        dispatchGroup.leave()\n                    }\n                } else {\n                    addQueue.async { [weak self] in\n                        guard let self = self else { return }\n                        _ = self.index.addFile(fileURL: fileURL, canReplace: true)\n                        dispatchGroup.leave()\n                    }\n                }\n            }\n\n            dispatchGroup.notify(queue: .main) {\n                if flushWhenComplete {\n                    self.index.flush()\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/Indexer/SearchIndexer+File.swift",
    "content": "//\n//  SearchIndexer+File.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 18.11.23.\n//\n\nimport Foundation\n\nextension SearchIndexer {\n    /// A file based index\n    public class File: SearchIndexer {\n        /// The file url where the index is located\n        public let fileURL: URL\n\n        private init(url: URL, index: SKIndex) {\n            self.fileURL = url\n            super.init(index: index)\n        }\n\n        /// Create a new file based index\n        /// - Parameters:\n        ///    - fileURL:The file URL to create the index at\n        ///    - properties: The properties defining the capabilities of the index\n        public convenience init?(fileURL: URL, properties: CreateProperties) {\n            if !FileManager.default.fileExists(atPath: fileURL.absoluteString),\n               let skIndex = SKIndexCreateWithURL(\n                fileURL as CFURL,\n                nil,\n                properties.indexType,\n                properties.properties()\n               ) {\n                self.init(url: fileURL, index: skIndex.takeUnretainedValue())\n            } else {\n                return nil\n            }\n        }\n\n        /// Load an index from a file url\n        /// - Parameter fileURL: The file URL where the index is located at\n        /// - Parameter writable: Can the index be modified\n        public convenience init?(fileURL: URL, writeable: Bool) {\n            if let skIndex = SKIndexOpenWithURL(fileURL as CFURL, nil, writeable) {\n                self.init(url: fileURL, index: skIndex.takeUnretainedValue())\n            } else {\n                return nil\n            }\n        }\n\n        /// Open an index from a file url.\n        ///\n        /// - Parameters:\n        ///   - fileURL: The file url to open\n        ///   - writable: should the index be modifiable?\n        /// - Returns: A new index object if successful, nil otherwise\n        public static func openIndex(fileURL: URL, writeable: Bool) -> SearchIndexer.File? {\n            if let temp = SKIndexOpenWithURL(fileURL as CFURL, nil, writeable) {\n                return SearchIndexer.File(url: fileURL, index: temp.takeUnretainedValue())\n            }\n            return nil\n        }\n\n        /// Create an indexer using a new data container for the store\n        ////\n        /// - Parameters:\n        ///    - fileURL: the file URL to store the index at.  url must be a non-existent file\n        ///    - properties: the properties for index creation\n        /// - Returns: A new index object if successful, nil otherwise. Returns nil if the file already exists at url.\n        public static func create(\n            fileURL: URL,\n            properties: CreateProperties = CreateProperties()\n        ) -> SearchIndexer.File? {\n            if !FileManager.default.fileExists(atPath: fileURL.absoluteString),\n               let skIndex = SKIndexCreateWithURL(\n                fileURL as CFURL,\n                nil,\n                properties.indexType,\n                properties.properties()\n               ) {\n                return SearchIndexer.File(url: fileURL, index: skIndex.takeUnretainedValue())\n            } else {\n                return nil\n            }\n        }\n\n        /// Flush, compact, i.e. apply all changes and write the content of the index to the file\n        public func save() {\n            flush()\n            compact()\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/Indexer/SearchIndexer+InternalMethods.swift",
    "content": "//\n//  SearchIndexer+InternalMethods.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 18.11.23.\n//\n\nimport Foundation\nimport UniformTypeIdentifiers\n\nextension SearchIndexer {\n    /// A \"typealias\" for a document ID, using a struct because swift lint doesn't allow type-aliases for 3 types\n    public struct DocumentID {\n        let url: URL\n        let document: SKDocument\n        let documentID: SKDocumentID\n    }\n    /// Returns the mime type for the url, or nil if the mime type couldn't be ascertained from the extension\n    ///\n    /// - Parameter url: the url to detect the mime type for\n    /// - Returns: the mime type of the url if able to detect, nil otherwise\n    func detectMimeType(_ url: URL) -> String? {\n        if let type = UTType(filenameExtension: url.pathExtension) {\n            if let mimeType = type.preferredMIMEType {\n                return mimeType\n            }\n        }\n        return nil\n    }\n\n    /// Remove the given document from the index\n    /// When the app deletes a document, use this function to update the index to reflect the change,\n    /// i. e. the index does not need to get flushed.\n    func remove(document: SKDocument) -> Bool {\n        if let index = self.index {\n            return modifyIndexQueue.sync {\n                SKIndexRemoveDocument(index, document)\n            }\n        }\n        return false\n    }\n\n    /// Returns the number of terms of the specified document\n    private func termCount(for document: SKDocumentID) -> Int {\n        guard self.index != nil else {\n            return 0\n        }\n        return SKIndexGetDocumentTermCount(self.index!, document)\n    }\n\n    /// Is the specified document empty (ie. it has no terms)\n    private func isEmpty(for document: SKDocumentID) -> Bool {\n        guard self.index != nil else {\n            return true // true would be the default value, i.e. document is Empty\n        }\n        return self.termCount(for: document) == 0\n    }\n\n    /// Recurse through the children of a document and return an array containing all the document-ids\n    private func addLeafURLs(index: SKIndex, inParentDocument: SKDocument?, docs: inout [DocumentID]) {\n        guard let index = self.index else {\n            return\n        }\n\n        var isLeaf = true\n\n        let iterator = SKIndexDocumentIteratorCreate(index, inParentDocument).takeRetainedValue()\n        while let skDocument = SKIndexDocumentIteratorCopyNext(iterator) {\n            isLeaf = false\n            self.addLeafURLs(index: index, inParentDocument: skDocument.takeRetainedValue(), docs: &docs)\n        }\n        if isLeaf, inParentDocument != nil,\n           kSKDocumentStateNotIndexed != SKIndexGetDocumentState(index, inParentDocument) {\n            let url = SKDocumentCopyURL(inParentDocument).takeRetainedValue()\n\n            let documentID = SKIndexGetDocumentID(index, inParentDocument)\n            docs.append(\n                DocumentID(\n                    url: url as URL,\n                    document: inParentDocument!,\n                    documentID: documentID\n                )\n            )\n\n        }\n    }\n\n    /// Return an array of all the documents contained within the index\n    ///\n    /// - Parameter termState: the TermState of documents to be returned (eg. all, empty only, non-empty only)\n    /// - Returns: An array containing all the documents matching the TermState\n    func fullDocuments(termState: TermState = .all) -> [DocumentID] {\n        guard let index = self.index else {\n            return []\n        }\n\n        var allDocs = [DocumentID]()\n\n        self.addLeafURLs(index: index, inParentDocument: nil, docs: &allDocs)\n\n        switch termState {\n        case .empty:\n            allDocs = allDocs.filter {\n                self.isEmpty(for: $0.documentID)\n            }\n        case .notEmpty:\n            allDocs = allDocs.filter {\n                !self.isEmpty(for: $0.documentID)\n            }\n        default:\n            break\n        }\n\n        return allDocs\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/Indexer/SearchIndexer+Memory.swift",
    "content": "//\n//  SearchIndexer+Memory.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 18.11.23.\n//\n\nimport Foundation\nextension SearchIndexer {\n    /// Memory based indexing using NSMutable\n    public class Memory: SearchIndexer {\n        // The data index store\n        private var store = NSMutableData()\n\n        /// Create a new in-memory index\n        /// - Parameter properties: the properties to use in the index\n        public init?(properties: CreateProperties = CreateProperties()) {\n            let data = NSMutableData()\n            if let skIndex = SKIndexCreateWithMutableData(\n                data,\n                nil,\n                properties.indexType,\n                properties.properties()\n            ) {\n                super.init(index: skIndex.takeUnretainedValue())\n                self.store = data\n            } else {\n                return nil\n            }\n        }\n\n        /// Create an in-memory index from the data provided\n        /// - Parameter data: The data to load the index data from\n        public convenience init?(data: Data) {\n            if let rawData = (data as NSData).mutableCopy() as? NSMutableData,\n               let skIndex = SKIndexOpenWithMutableData(rawData, nil) {\n                self.init(data: rawData, index: skIndex.takeUnretainedValue())\n            } else {\n                return nil\n            }\n        }\n\n        /// Create an indexer using a new data container for the store\n        ///\n        /// - Parameter properties: the properties for index creation\n        /// - Returns: A new index object if successful, nil otherwise\n        public static func create(properties: CreateProperties = CreateProperties()) -> SearchIndexer.Memory? {\n            let data = NSMutableData()\n            if let skIndex = SKIndexCreateWithMutableData(\n                data,\n                nil,\n                properties.indexType,\n                properties.properties()\n            ) {\n                return SearchIndexer.Memory(data: data, index: skIndex.takeUnretainedValue())\n            }\n            return nil\n        }\n\n        /// Create an indexer using the data stored in 'data'.\n        ///\n        /// **NOTE** Makes a copy of the data first - does not work on a live Data object\n        ///\n        /// - Parameter data: The data to load as an index\n        /// - Returns: A new index object if successful, nil otherwise\n        public static func loadFromData(data: Data) -> SearchIndexer.Memory? {\n            if let rawData = (data as NSData).mutableCopy() as? NSMutableData,\n               let skIndex = SKIndexOpenWithMutableData(rawData, nil) {\n                return SearchIndexer.Memory(data: rawData, index: skIndex.takeUnretainedValue())\n            }\n            return nil\n        }\n\n        /// Returns a copy of the index as data\n        public func getAsData() -> Data? {\n            flush()\n            return self.store.copy() as? Data\n        }\n\n        private init(data: NSMutableData, index: SKIndex) {\n            super.init(index: index)\n            self.store = data\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/Indexer/SearchIndexer+ProgressiveSearch.swift",
    "content": "//\n//  SearchIndexer+ProgressiveSearch.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 18.11.23.\n//\n\nimport Foundation\n\nextension SearchIndexer {\n    /// Object representing the search results\n    public class SearchResult {\n        /// The identifying url for the document\n        let url: URL\n\n        /// The search score for the document, higher means more relevant\n        let score: Float\n\n        init(url: URL, score: Float) {\n            self.url = url\n            self.score = score\n        }\n    }\n\n    /// Start a progressive search\n    public func progressiveSearch(\n        query: String,\n        options: SKSearchOptions = SKSearchOptions(kSKSearchOptionDefault)\n    ) -> ProgressiveSearch {\n        return ProgressiveSearch(options: options, index: self, query: query)\n    }\n\n    /// A class for creating and managing a progressive search.\n    /// A search starts on creation and can be cancelled at any time.\n    public class ProgressiveSearch {\n        /// A class representing the results of a search request.\n        public class Results {\n            /// Create a search result\n            ///\n            /// - Parameters:\n            ///   - moreResultsAvailable: A boolean indicating whether more search results are available\n            ///   - results: The partial results for the search request\n            public init(moreResultsAvailable: Bool, results: [SearchResult]) {\n                self.moreResultsAvailable = moreResultsAvailable\n                self.results = results\n            }\n\n            /// A boolean indicating whether more search results are available\n            public let moreResultsAvailable: Bool\n\n            /// The partial results for the search request\n            public let results: [SearchResult]\n        }\n\n        private let options: SKSearchOptions\n        private let search: SKSearch\n        private let index: SearchIndexer\n        private let query: String\n\n        init(options: SKSearchOptions, index: SearchIndexer, query: String) {\n            self.options = options\n            self.search = SKSearchCreate(index.index, query as CFString, options).takeRetainedValue()\n            self.index = index\n            self.query = query\n        }\n\n        /// Retrieves the next chunk of search results in a progressive search.\n        ///\n        /// - Parameters:\n        ///   - limit: The maximum number of results to retrieve in each call. Defaults to 10.\n        ///   - timeout: The duration to wait for the search to complete before stopping. Defaults to 1.0 seconds.\n        ///\n        /// - Returns: A tuple containing search results and information about the progress of the search.\n        ///\n        /// The function performs a progressive search,\n        /// fetching the next set of results based on the specified limit and timeout.\n        /// It uses the Search Kit framework to find matches, retrieve document URLs, and their corresponding scores.\n        public func getNextSearchResultsChunk(\n            limit: Int = 10,\n            timeout: TimeInterval = 1.0\n        ) -> (ProgressiveSearch.Results) {\n            guard self.index.index != nil else {\n                return Results(moreResultsAvailable: false, results: [])\n            }\n\n            var scores: [Float] = Array(repeating: 0.0, count: limit)\n            var urls: [Unmanaged<CFURL>?] = Array(repeating: nil, count: limit)\n            var documentIDs: [SKDocumentID] = Array(repeating: 0, count: limit)\n            var foundCount = 0\n\n            let hasMore = SKSearchFindMatches(self.search, limit, &documentIDs, &scores, timeout, &foundCount)\n            SKIndexCopyDocumentURLsForDocumentIDs(self.index.index, foundCount, &documentIDs, &urls)\n\n            let partialResult: [SearchResult] = zip(urls[0..<foundCount], scores)\n                .compactMap { (cfurl, score) -> SearchResult? in\n                    guard let url  = cfurl?.takeRetainedValue() as URL? else {\n                        return nil\n                    }\n\n                    return SearchResult(url: url, score: score)\n                }\n\n            return Results(moreResultsAvailable: hasMore, results: partialResult)\n        }\n\n        /// Cancel an active search\n        public func cancel() {\n            SKSearchCancel(self.search)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/Indexer/SearchIndexer+Search.swift",
    "content": "//\n//  SearchIndexer+Search.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 18.11.23.\n//\n\nimport Foundation\n\nextension SearchIndexer {\n    /// Initiates a search operation based on the provided query.\n    ///\n    /// - Parameters:\n    ///   - query: A string representing the term to be searched for.\n    ///   - limit: The maximum number of search results to be returned.\n    ///   - timeout: The duration to wait for the search to complete before stopping.\n    ///\n    /// - Returns: \n    ///     An array of search results, each containing a match URL and its corresponding score,\n    ///     indicating the relevance of the match to the query.\n    ///\n    /// The function performs a search using the specified query,\n    /// limiting the number of results based on the provided `limit`.\n    /// The `timeout` parameter determines how long the search operation will wait before stopping.\n    public func search(\n        _ query: String,\n        limit: Int = 10,\n        timeout: TimeInterval = 1.0,\n        options: SKSearchOptions = SKSearchOptions(kSKSearchOptionDefault)\n    ) -> [SearchResult] {\n        let search = self.progressiveSearch(query: query, options: options)\n\n        var results: [SearchResult] = []\n        var moreResultsAvailable = true\n        repeat {\n            let result = search.getNextSearchResultsChunk(limit: limit, timeout: timeout)\n            results.append(contentsOf: result.results)\n            moreResultsAvailable = result.moreResultsAvailable\n        } while moreResultsAvailable\n\n        return results\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/Indexer/SearchIndexer+Terms.swift",
    "content": "//\n//  SearchIndexer+Terms.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 18.11.23.\n//\n\nimport Foundation\n\nextension SearchIndexer {\n    /// A class to contain a term and the count of times it appears\n    public class TermCount {\n        /// A term within the document\n        public let term: String\n\n        /// The number of occurrences of `term`\n        public let count: Int\n\n        init(term: String, count: Int) {\n            self.term = term\n            self.count = count\n        }\n    }\n\n    /// A enum to specify the state of the document\n    public enum TermState: Int {\n        /// All document states\n        case all = 0\n        /// Only documents that have no terms\n        case empty = 1\n        /// Only documents that have terms\n        case notEmpty = 2\n    }\n\n    /// Returns all the document URLs loaded into the index matching the specified term state\n    ///\n    /// - Parameter termState: Only return documents matching the specified document state\n    /// - Returns: An array containing all the document URLs\n    public func documents(termState: TermState = .all) -> [URL] {\n        return self.fullDocuments(termState: termState).map { $0.url }\n    }\n\n    /// Returns the number of terms for the specified document url\n    public func termCount(for url: URL) -> Int {\n        if let index = self.index,\n           let document = SKDocumentCreateWithURL(url as CFURL) {\n            let documentID = SKIndexGetDocumentID(index, document.takeUnretainedValue())\n            return SKIndexGetDocumentTermCount(index, documentID)\n        }\n        return 0\n    }\n\n    /// Is the specified document empty (ie. it has no terms)\n    public func isEmpty(for url: URL) -> Bool {\n        return self.termCount(for: url) > 0\n    }\n\n    /// Returns an array containing the terms and counts for a specified URL\n    ///\n    /// - Parameter url: The document URL in the index to locate\n    /// - Returns: An array of the terms and corresponding counts located in the document.\n    ///            Returns an empty array if the document cannot be located.\n    public func terms(for url: URL) -> [TermCount] {\n        guard let index = self.index else {\n            return []\n        }\n\n        var result = [TermCount]()\n\n        let document = SKDocumentCreateWithURL(url as CFURL).takeRetainedValue()\n        let documentID = SKIndexGetDocumentID(index, document)\n\n        guard let termVals = SKIndexCopyTermIDArrayForDocumentID(index, documentID),\n              let terms = termVals.takeRetainedValue() as? [CFIndex] else {\n            return []\n        }\n\n        for term in terms {\n            if let termVal = SKIndexCopyTermStringForTermID(index, term) {\n                let termString = termVal.takeUnretainedValue() as String\n                if !self.stopWords.contains(termString) {\n                    let count = SKIndexGetDocumentTermFrequency(index, documentID, term) as Int\n                    result.append(TermCount(term: termString, count: count))\n                }\n            }\n        }\n\n        return result\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/Indexer/SearchIndexer.swift",
    "content": "//\n//  SearchIndexer.swift\n//  CodeEdit\n//\n//  Created by Tom Ludwig on 18.11.23.\n//\n\nimport Foundation\n\n/// Indexer using SKIndex\npublic class SearchIndexer {\n    let modifyIndexQueue = DispatchQueue(label: \"app.codeedit.CodeEdit.ModifySearchIndex\")\n\n    var index: SKIndex?\n\n    init(index: SKIndex) {\n        self.index = index\n    }\n\n    deinit {\n        self.close()\n    }\n\n    /// Flush any pending commands to the search index. Flush should always be called before performing a search\n    public func flush() {\n        if let index = self.index {\n            SKIndexFlush(index)\n        }\n    }\n\n    /// Reduce the size of index where possible\n    ///\n    /// - Warning: Do NOT call on the main thread\n    public func compact() {\n        if let index = self.index {\n            SKIndexCompact(index)\n        }\n    }\n\n    /// Remove any documents that have no search terms\n    public func cleanUp() -> Int {\n        let allDocs = self.fullDocuments(termState: .empty)\n        var removedCount = 0\n        for docID in allDocs {\n            _ = self.remove(document: docID.document)\n            removedCount += 1\n        }\n        return removedCount\n    }\n\n    /// Close the index\n    public func close() {\n        if let index = self.index {\n            SKIndexClose(index)\n            self.index = nil\n        }\n    }\n\n    /// Call  once at application launch to tell Search Kit to use the Spotlight metadata importers.\n    lazy var dataExtractorLoaded: Bool = {\n        SKLoadDefaultExtractorPlugIns()\n        return true\n    }()\n\n    /// Stop words for the index, \n    /// these are common words which should be ignored because they are not useful for searching\n    private(set) lazy var stopWords: Set<String> = {\n        var stopWords: Set<String> = []\n        if let index = self.index,\n           let properties = SKIndexGetAnalysisProperties(self.index).takeRetainedValue() as? [String: Any],\n           let newStopWords = properties[kSKStopWords as String] as? Set<String> {\n            stopWords = newStopWords\n        }\n        return stopWords\n    }()\n\n    public enum IndexType: UInt32 {\n        /// Unknown index type (kSKIndexUnknown)\n        case unknown = 0\n        /// Inverted index, mapping terms to documents (kSKIndexInverted)\n        case inverted = 1\n        /// Vector index, mapping documents to terms (kSKIndexVector)\n        case vector = 2\n        /// Index type with all the capabilities of an inverted and a vector index (kSKIndexInvertedVector)\n        case invertedVector = 3\n    }\n\n    /// A class for creating properties used in the creation of a Search Kit index.\n    /// **Available Options:**\n    /// - `indexType`: The type of the index to be created.\n    ///     Options include `.unknown`, `.inverted`, `.vector` or `.invertedVector`\n    /// - `proximityIndexing`: A Boolean flag indicating whether or not Search Kit should use proximity indexing.\n    /// - `stopWords`: A set of stop-words — words not to index.\n    /// - `minTermLength`: The minimum term length to index (defaults to 1).\n    public class CreateProperties {\n        /// The type of the index to be created\n        private(set) var indexType: SKIndexType = kSKIndexInverted\n        /// Whether the index should use proximity indexing\n        private(set) var proximityIndexing: Bool = false\n        /// The stop words for the index\n        private(set) var stopWords: Set<String> = Set<String>()\n        /// The minimum size of word to add to the index\n        private(set) var minTermLength: UInt = 1\n\n        /// Create a properties object with the specified creation parameters\n        ///\n        /// - Parameters:\n        ///   - indexType: The type of index\n        ///   - proximityIndexing: A Boolean flag indicating whether or not Search Kit should use proximity indexing\n        ///   - stopWords: A set of stop-words — words not to index\n        ///   - minTermLength: The minimum term length to index (defaults to 1)\n        public init(\n            indexType: SearchIndexer.IndexType = .inverted,\n            proximityIndexing: Bool = false,\n            stopWords: Set<String> = [],\n            minTermLength: UInt = 1\n        ) {\n            self.indexType = SKIndexType(indexType.rawValue)\n            self.proximityIndexing = proximityIndexing\n            self.stopWords = stopWords\n            self.minTermLength = minTermLength\n        }\n\n        /// Returns a CFDictionary object to use for the call to SKIndexCreate\n        func properties() -> CFDictionary {\n            let properties: [CFString: Any] = [\n                kSKProximityIndexing: self.proximityIndexing,\n                kSKStopWords: self.stopWords,\n                kSKMinTermLength: self.minTermLength,\n            ]\n            return properties as CFDictionary\n        }\n    }\n\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument+Find.swift",
    "content": "//\n//  WorkspaceDocument+Find.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 02.01.24.\n//\n\nimport Foundation\n\nextension WorkspaceDocument.SearchState: @unchecked Sendable {}\n\nextension WorkspaceDocument.SearchState {\n    /// Creates a search term based on the given query and search mode.\n    ///\n    /// - Parameter query: The original user query string.\n    ///\n    /// - Returns: A modified search term according to the specified search mode.\n    func getSearchTerm(_ query: String) -> String {\n        let newQuery = stripSpecialCharacters(from: (caseSensitive ? query : query.lowercased()))\n        guard let mode = selectedMode.third else {\n            return newQuery\n        }\n\n        switch mode {\n        case .Containing:\n            return \"*\\(newQuery)*\"\n        case .StartingWith:\n            return \"\\(newQuery)*\"\n        case .EndingWith:\n            return \"*\\(newQuery)\"\n        default:\n            return newQuery\n        }\n    }\n\n    func stripSpecialCharacters(from string: String) -> String {\n        let regex = try? NSRegularExpression(pattern: \"[^a-zA-Z0-9]+\", options: .caseInsensitive)\n        return regex!.stringByReplacingMatches(\n            in: string,\n            options: [],\n            range: NSRange(location: 0, length: string.utf16.count),\n            withTemplate: \"*\"\n        )\n    }\n\n    /// Generates a regular expression pattern based on the specified query and search mode.\n    ///\n    /// - Parameter query: The original user query string.\n    ///\n    /// - Returns: A string representing the regular expression pattern based on the selected search mode.\n    ///\n    /// - Note: This function is creating similar patterns to the\n    /// ``WorkspaceDocument/SearchState-swift.class/getSearchTerm(_:)`` function,\n    /// Except its using the word boundary anchor(\\b) instead of the asterisk(\\*).\n    /// This is needed to highlight the search results correctly.\n    func getRegexPattern(_ query: String) -> String {\n        let newQuery = NSRegularExpression.escapedPattern(for: query.trimmingCharacters(in: .whitespacesAndNewlines))\n\n        guard let mode = selectedMode.third else {\n            return newQuery\n        }\n\n        switch mode {\n        case .Containing:\n            return \"\\(newQuery)\"\n        case .StartingWith:\n            return \"\\\\b\\(newQuery)\"\n        case .EndingWith:\n            return \"\\(newQuery)\\\\b\"\n        case .MatchingWord:\n            return \"\\\\b\\(newQuery)\\\\b\"\n        default:\n            return newQuery\n        }\n    }\n\n    /// Searches the entire workspace for the given string, using the\n    /// ``WorkspaceDocument/SearchState-swift.class/selectedMode`` modifiers\n    /// to modify the search if needed. This is done by filtering out files with SearchKit and then searching\n    /// within each file for the given string.\n    ///\n    /// This method will update\n    /// ``WorkspaceDocument/SearchState-swift.class/searchResult``,\n    /// ``WorkspaceDocument/SearchState-swift.class/searchResultsFileCount``\n    /// and ``WorkspaceDocument/SearchState-swift.class/searchResultCount`` with any matched\n    /// search results. See ``SearchResultModel`` and ``SearchResultMatchModel``\n    /// for more information on search results and matches.\n    ///\n    /// - Parameter query: The search query to search for.\n    func search(_ query: String) async {\n        clearResults()\n\n        await MainActor.run {\n            self.searchQuery = query\n            self.findNavigatorStatus = .searching\n        }\n\n        let searchQuery = getSearchTerm(query)\n        let regexPattern = getRegexPattern(query)\n\n        guard let indexer = indexer else {\n            await setStatus(.failed(errorMessage: \"No index found. Try rebuilding the index.\"))\n            return\n        }\n\n        let asyncController = SearchIndexer.AsyncManager(index: indexer)\n        let evaluateResultGroup = DispatchGroup()\n        let evaluateSearchQueue = DispatchQueue(label: \"app.codeedit.CodeEdit.EvaluateSearch\")\n\n        let searchStream = await asyncController.search(query: searchQuery, 20)\n        for try await result in searchStream {\n            for file in result.results {\n                let fileURL = file.url\n                let fileScore = file.score\n                let capturedRegexPattern = regexPattern\n\n                evaluateSearchQueue.async(group: evaluateResultGroup) {\n                    evaluateResultGroup.enter()\n                    Task { [weak self] in\n                        guard let self else {\n                            evaluateResultGroup.leave()\n                            return\n                        }\n\n                        let result = await self.evaluateSearchResult(\n                            fileURL: fileURL,\n                            fileScore: fileScore,\n                            regexPattern: capturedRegexPattern\n                        )\n\n                        if let result = result {\n                            await self.appendNewResultsToTempResults(newResult: result)\n                        }\n                        evaluateResultGroup.leave()\n                    }\n                }\n            }\n        }\n\n        evaluateResultGroup.notify(queue: evaluateSearchQueue) {\n            Task { @MainActor [weak self] in\n                self?.setSearchResults()\n            }\n        }\n    }\n\n    /// Appends a new search result to the temporary search results array on the main thread.\n    ///\n    /// - Parameters:\n    ///   - newResult: The `SearchResultModel` to be appended to the temporary search results.\n    @MainActor\n    func appendNewResultsToTempResults(newResult: SearchResultModel) {\n        self.tempSearchResults.append(newResult)\n    }\n\n    /// Sets the search results by updating various properties on the main thread.\n    /// This function updates `findNavigatorStatus`, `searchResult`, `searchResultCount`, and `searchResultsFileCount`\n    /// and sets the `tempSearchResults` to an empty array.\n    /// - Important: Call this function when you are ready to\n    /// display or use the final search results.\n    @MainActor\n    func setSearchResults() {\n        self.searchResult = self.tempSearchResults.sorted { $0.score > $1.score }\n        self.searchResultsCount = self.tempSearchResults.map { $0.lineMatches.count }.reduce(0, +)\n        self.searchResultsFileCount = self.tempSearchResults.count\n        self.findNavigatorStatus = .found\n        self.tempSearchResults = []\n    }\n\n    /// Evaluates a search query within the content of a file and updates\n    /// the provided `SearchResultModel` with matching occurrences.\n    ///\n    /// - Parameters:\n    ///   - query: The search query to be evaluated, potentially containing a regular expression.\n    ///   - searchResult: The `SearchResultModel` object to be updated with the matching occurrences.\n    ///\n    /// This function retrieves the content of a file specified in the `searchResult` parameter\n    /// and applies a search query using a regular expression.\n    /// It then iterates over the matches found in the file content,\n    /// creating `SearchResultMatchModel` instances for each match.\n    /// The resulting matches are appended to the `lineMatches` property of the `searchResult`.\n    /// Line matches are the preview lines that are shown in the search results.\n    ///\n    /// # Example Usage\n    /// ```swift\n    /// var resultModel = SearchResultModel()\n    /// await evaluateFile(query: \"example\", searchResult: &resultModel)\n    /// ```\n    private func evaluateFile(query: String, searchResult: inout SearchResultModel) async {\n        guard let data = try? Data(contentsOf: searchResult.file.url) else {\n            return\n        }\n        guard let fileContent = String(bytes: data, encoding: .utf8) else {\n            await setStatus(.failed(errorMessage: \"Failed to decode file content.\"))\n            return\n        }\n\n        // Attempt to create a regular expression from the provided query\n        guard let regex = try? NSRegularExpression(\n            pattern: query,\n            options: caseSensitive ? [] : .caseInsensitive\n        ) else {\n            await setStatus(.failed(errorMessage: \"Invalid regular expression.\"))\n            return\n        }\n\n        // Find all matches of the query within the file content using the regular expression\n        let matches = regex.matches(in: fileContent, range: NSRange(location: 0, length: fileContent.utf16.count))\n\n        var newMatches = [SearchResultMatchModel]()\n\n        // Process each match and add it to the array of `newMatches`\n        for match in matches {\n            if let matchRange = Range(match.range, in: fileContent) {\n                let matchWordLength = match.range.length\n                let matchModel = createMatchModel(\n                    from: matchRange,\n                    fileContent: fileContent,\n                    file: searchResult.file,\n                    matchWordLength: matchWordLength\n                )\n                newMatches.append(matchModel)\n            }\n        }\n\n        searchResult.lineMatches = newMatches\n    }\n\n    /// Creates a `SearchResultMatchModel` instance based on the provided parameters,\n    /// representing a matching occurrence within a file.\n    ///\n    /// - Parameters:\n    ///   - matchRange: The range of the matched substring within the entire file content.\n    ///   - fileContent: The content of the file where the match was found.\n    ///   - file: The `CEWorkspaceFile` object representing the file containing the match.\n    ///   - matchWordLength: The length of the matched substring.\n    ///\n    /// - Returns: A `SearchResultMatchModel` instance representing the matching occurrence.\n    ///\n    /// This function is responsible for constructing a `SearchResultMatchModel`\n    /// based on the provided parameters. It extracts the relevant portions of the file content,\n    /// including the lines before and after the match, and combines them into a final line.\n    /// The resulting model includes information about the match's range within the file,\n    /// the file itself, the content of the line containing the match,\n    /// and the range of the matched keyword within that line.\n    private func createMatchModel(\n        from matchRange: Range<String.Index>,\n        fileContent: String,\n        file: CEWorkspaceFile,\n        matchWordLength: Int\n    ) -> SearchResultMatchModel {\n        let preLine = extractPreLine(from: matchRange, fileContent: fileContent)\n        let keywordRange = extractKeywordRange(from: preLine, matchWordLength: matchWordLength)\n        let postLine = extractPostLine(from: matchRange, fileContent: fileContent)\n\n        let finalLine = preLine + postLine\n\n        return SearchResultMatchModel(\n            rangeWithinFile: matchRange,\n            file: file,\n            lineContent: finalLine,\n            keywordRange: keywordRange\n        )\n    }\n\n    /// Extracts the line preceding a matching occurrence within a file.\n    ///\n    /// - Parameters:\n    ///   - matchRange: The range of the matched substring within the entire file content.\n    ///   - fileContent: The content of the file where the match was found.\n    ///\n    /// - Returns: A string representing the line preceding the match.\n    ///\n    /// This function retrieves the line preceding a matching occurrence within the provided file content.\n    /// It considers a context of up to 60 characters before the match and clips the result to the last\n    /// occurrence of a newline character, ensuring that only the line containing the search term is displayed.\n    /// The extracted line is then trimmed of leading and trailing whitespaces and\n    /// newline characters before being returned.\n    private func extractPreLine(from matchRange: Range<String.Index>, fileContent: String) -> String {\n        let preRangeStart = fileContent.index(\n            matchRange.lowerBound,\n            offsetBy: -60,\n            limitedBy: fileContent.startIndex\n        ) ?? fileContent.startIndex\n\n        let preRangeEnd = matchRange.upperBound\n        let preRange = preRangeStart..<preRangeEnd\n\n        let preLineWithNewLines = fileContent[preRange]\n        // Clip the range of the preview to the last occurrence of a new line\n        let lastNewLineIndexInPreLine = preLineWithNewLines.lastIndex(of: \"\\n\") ?? preLineWithNewLines.startIndex\n        let preLineWithNewLinesPrefix = preLineWithNewLines[lastNewLineIndexInPreLine...]\n\n        return preLineWithNewLinesPrefix.trimmingCharacters(in: .whitespacesAndNewlines)\n    }\n\n    /// Extracts the range of the search term within the line preceding a matching occurrence.\n    ///\n    /// - Parameters:\n    ///   - preLine: The line preceding the matching occurrence within the file content.\n    ///   - matchWordLength: The length of the search term.\n    ///\n    /// - Returns: A range representing the position of the search term within the `preLine`.\n    ///\n    /// This function calculates the range of the search term within\n    /// the provided line preceding a matching occurrence.\n    /// It considers the length of the search term to determine\n    /// the lower and upper bounds of the keyword range within the line.\n    private func extractKeywordRange(from preLine: String, matchWordLength: Int) -> Range<String.Index> {\n        let keywordLowerBound = preLine.index(\n            preLine.endIndex,\n            offsetBy: -matchWordLength,\n            limitedBy: preLine.startIndex\n        ) ?? preLine.endIndex\n        let keywordUpperBound = preLine.endIndex\n\n        return keywordLowerBound..<keywordUpperBound\n    }\n\n    /// Extracts the line following a matching occurrence within a file.\n    ///\n    /// - Parameters:\n    ///   - matchRange: The range of the matched substring within the entire file content.\n    ///   - fileContent: The content of the file where the match was found.\n    ///\n    /// - Returns: A string representing the line following the match.\n    ///\n    /// This function retrieves the line following a matching occurrence within the provided file content.\n    /// It considers a context of up to 60 characters after the match and clips the result to the first\n    /// occurrence of a newline character, ensuring that only the relevant portion of the line is displayed.\n    /// The extracted line is then converted to a string before being returned.\n    private func extractPostLine(from matchRange: Range<String.Index>, fileContent: String) -> String {\n        let postRangeStart = matchRange.upperBound\n        let postRangeEnd = fileContent.index(\n            matchRange.upperBound,\n            offsetBy: 60,\n            limitedBy: fileContent.endIndex\n        ) ?? fileContent.endIndex\n\n        let postRange = postRangeStart..<postRangeEnd\n        let postLineWithNewLines = fileContent[postRange]\n\n        let firstNewLineIndexInPostLine = postLineWithNewLines.firstIndex(of: \"\\n\") ?? postLineWithNewLines.endIndex\n        return String(postLineWithNewLines[..<firstNewLineIndexInPostLine])\n    }\n\n    /// Resets the search results along with counts for overall results and file-specific results.\n    func clearResults() {\n        DispatchQueue.main.async {\n            self.searchResult.removeAll()\n            self.searchResultsCount = 0\n            self.searchResultsFileCount = 0\n            self.findNavigatorStatus = .none\n        }\n    }\n\n    /// Evaluates a matched file to determine if it contains any search matches.\n    /// Requires a file score from the search model.\n    ///\n    /// Evaluates the file's contents asynchronously.\n    ///\n    /// - Parameters:\n    ///   - fileURL: The `URL` of the file to evaluate.\n    ///   - fileScore:  The file's score from a ``SearchIndexer``\n    ///   - regexPattern: The pattern to evaluate against the file's contents.\n    /// - Returns: `nil` if there are no relevant search matches, or a search result if matches are found.\n    private func evaluateSearchResult(\n        fileURL: URL,\n        fileScore: Float,\n        regexPattern: String\n    ) async -> SearchResultModel? {\n        var newResult = SearchResultModel(\n            file: CEWorkspaceFile(url: fileURL),\n            score: fileScore\n        )\n\n        await evaluateFile(query: regexPattern, searchResult: &newResult)\n\n        return newResult.lineMatches.isEmpty ? nil : newResult\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument+FindAndReplace.swift",
    "content": "//\n//  WorkspaceDocument+FindAndReplace.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 02.01.24.\n//\n\nimport Foundation\nimport AppKit\n\nextension WorkspaceDocument.SearchState {\n    /// Performs a search and replace operation in a collection of files based on the provided query.\n    ///\n    /// - Parameters:\n    ///   - query: The search query to look for in the files.\n    ///   - replacingTerm: The term to replace the matched query with.\n    ///\n    /// - Important: This function relies on an indexer and assumes that it has been previously set.\n    /// If the indexer is not available, the function will return early.\n    /// Also make sure to flush any pending changes to the index before calling this function.\n    func findAndReplace(query: String, replacingTerm: String) async throws {\n        await setStatus(.replacing)\n        let searchQuery = getSearchTerm(query)\n        guard let indexer = indexer else { return }\n\n        var errorCount: Int = 0, updatedFilesCount: Int = 0\n        let asyncController = SearchIndexer.AsyncManager(index: indexer)\n\n        let searchStream = await asyncController.search(query: searchQuery, 20)\n\n        for try await result in searchStream {\n            await withTaskGroup(of: Bool.self) { taskGroup in\n                for file in result.results {\n                    taskGroup.addTask {\n                        do {\n                            try await self.replaceOccurrencesInFile(\n                                fileURL: file.url,\n                                query: query,\n                                replacingTerm: replacingTerm\n                            )\n                            return true\n                        } catch {\n                            // TODO: Write log; issue number: #0000\n                            return false\n                        }\n                    }\n                }\n\n                for await taskGroupResults in taskGroup {\n                    if taskGroupResults {\n                        updatedFilesCount += 1\n                    } else {\n                        errorCount += 1\n                    }\n                }\n            }\n        }\n\n        // Display the replacing results to the user\n        if updatedFilesCount == 0 && errorCount == 0 {\n            // No results where found\n            await setStatus(.failed(errorMessage: \"No files in the workspace matched: \\(query)\"))\n        } else if updatedFilesCount == 0 && errorCount > 0 {\n            // All files failed to updated\n            await setStatus(\n                .failed(\n                    errorMessage: \"All files failed to update. (\\(errorCount)) \" +\n                    \"errors occurred. Check logs for more information\"\n                )\n            )\n        } else if updatedFilesCount > 0 && errorCount > 0 {\n            // Some files updated successfully, some failed\n            await setStatus(\n                .failed(\n                    errorMessage: \"\\(updatedFilesCount) successfully updated, \" +\n                    \"\\(errorCount) errors occurred. Please check logs for more information.\"\n                )\n            )\n        } else {\n            // Each files updated successfully\n            await setStatus(.replaced(updatedFiles: updatedFilesCount))\n        }\n    }\n\n    /// Replaces occurrences of a specified query with a given replacement term in the content of a file.\n    ///\n    /// - Parameters:\n    ///     - fileURL: The URL of the file to be processed.\n    ///     - query:  The string to be searched for and replaced.\n    ///     - replacingTerm: The string to replace occurrences of the query.\n    ///     - options: The options for the search and replace operation.\n    ///             You can use options such as regular expression or case-insensitivity.\n    ///\n    /// The function performs the replacement in memory and then writes the modified content back to the file\n    func replaceOccurrencesInFile(\n        fileURL: URL,\n        query: String,\n        replacingTerm: String\n    ) async throws {\n        let fileContent = try String(contentsOf: fileURL, encoding: .utf8)\n        let updatedContent = fileContent.replacingOccurrences(\n            of: query,\n            with: replacingTerm,\n            options: self.replaceOptions\n        )\n\n        try updatedContent.write(to: fileURL, atomically: true, encoding: .utf8)\n    }\n\n    /// Replaces a specified range of text within a file with a new string.\n    ///\n    /// - Parameters:\n    ///   - file: The URL of the file to be modified.\n    ///   - searchTerm: The string to be replaced within the specified range.\n    ///   - replacingTerm: The string to replace the specified searchTerm.\n    ///   - keywordRange: The range within which the replacement should occur.\n    ///\n    /// - Note: This function  can be utilised for two specific use cases:\n    ///         1. To replace a particular occurrence of a string within a file,\n    ///         provide the range of the keyword to be replaced.\n    ///         2. To replace all occurrences of the string within the file,\n    ///         pass the start and end index covering the entire range.\n    func replaceRange(\n        file: URL,\n        searchTerm: String,\n        replacingTerm: String,\n        keywordRange: Range<String.Index>\n    ) {\n        guard let fileContent = try? String(contentsOf: file, encoding: .utf8) else {\n            let alert = NSAlert()\n            alert.messageText = \"Error\"\n            alert.informativeText = \"An error occurred while reading file contents of: \\(file)\"\n            alert.alertStyle = .critical\n            alert.addButton(withTitle: \"OK\")\n            alert.runModal()\n\n            return\n        }\n\n        var replaceOptions = NSString.CompareOptions()\n        if selectedMode.second == .RegularExpression {\n            replaceOptions = [.regularExpression]\n        }\n        if !caseSensitive {\n            replaceOptions = [.caseInsensitive]\n        }\n\n        let updatedContent = fileContent.replacingOccurrences(\n            of: searchTerm,\n            with: replacingTerm,\n            options: replaceOptions,\n            range: keywordRange\n        )\n\n        do {\n            try updatedContent.write(to: file, atomically: true, encoding: .utf8)\n        } catch {\n            let alert = NSAlert()\n            alert.messageText = \"Error\"\n            alert.informativeText = \"An error occurred while writing to: \\(error.localizedDescription)\"\n            alert.alertStyle = .critical\n            alert.addButton(withTitle: \"OK\")\n            alert.runModal()\n        }\n    }\n\n    func setStatus(_ status: FindNavigatorStatus) async {\n        await MainActor.run {\n            self.findNavigatorStatus = status\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument+Index.swift",
    "content": "//\n//  WorkspaceDocument+Index.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 02.01.24.\n//\n\nimport Foundation\n\nextension WorkspaceDocument.SearchState {\n    /// Adds the contents of the current workspace URL to the search index.\n    /// That means that the contents of the workspace will be indexed and searchable.\n    func addProjectToIndex() {\n        guard let indexer = indexer else { return }\n        guard let url = workspace.fileURL else { return }\n\n        indexStatus = .indexing(progress: 0.0)\n        let uuidString = UUID().uuidString\n        let createInfo: [String: Any] = [\n            \"id\": uuidString,\n            \"action\": \"create\",\n            \"title\": \"Indexing | Processing files\",\n            \"message\": \"Creating an index to enable fast and accurate searches within your codebase.\",\n            \"isLoading\": true\n        ]\n        NotificationCenter.default.post(name: .taskNotification, object: nil, userInfo: createInfo)\n\n        Task.detached {\n            let filePaths = self.getFileURLs(at: url)\n\n            let asyncController = SearchIndexer.AsyncManager(index: indexer)\n            var lastProgress: Double = 0\n\n            for await (file, index) in AsyncFileIterator(fileURLs: filePaths) {\n                _ = await asyncController.addText(files: [file], flushWhenComplete: false)\n                let progress = Double(index) / Double(filePaths.count)\n\n                // Send only if difference is > 0.5%, to keep updates from sending too frequently\n                if progress - lastProgress > 0.005 || index == filePaths.count - 1 {\n                    lastProgress = progress\n                    await MainActor.run {\n                        self.indexStatus = .indexing(progress: progress)\n                    }\n                    let updateInfo: [String: Any] = [\n                        \"id\": uuidString,\n                        \"action\": \"update\",\n                        \"percentage\": progress\n                    ]\n                    NotificationCenter.default.post(name: .taskNotification, object: nil, userInfo: updateInfo)\n                }\n            }\n            asyncController.index.flush()\n\n            await MainActor.run {\n                self.indexStatus = .done\n            }\n            let updateInfo: [String: Any] = [\n                \"id\": uuidString,\n                \"action\": \"update\",\n                \"title\": \"Finished indexing\",\n                \"isLoading\": false\n            ]\n            NotificationCenter.default.post(name: .taskNotification, object: nil, userInfo: updateInfo)\n\n            let deleteInfo = [\n                \"id\": uuidString,\n                \"action\": \"deleteWithDelay\",\n                \"delay\": 4.0\n            ]\n            NotificationCenter.default.post(name: .taskNotification, object: nil, userInfo: deleteInfo)\n        }\n    }\n\n    /// Retrieves an array of file URLs within the specified directory URL.\n    ///\n    /// - Parameter url: The URL of the directory to search for files.\n    ///\n    /// - Returns: An array of file URLs found within the specified directory.\n    func getFileURLs(at url: URL) -> [URL] {\n        let enumerator = FileManager.default.enumerator(\n            at: url,\n            includingPropertiesForKeys: [.isRegularFileKey],\n            options: [.skipsHiddenFiles, .skipsPackageDescendants]\n        )\n        return enumerator?.allObjects as? [URL] ?? []\n    }\n\n    /// Retrieves the contents of a files  from the specified file paths.\n    ///\n    /// - Parameter filePaths: An array of file URLs representing the paths of the files.\n    ///\n    /// - Returns: An array of `TextFile` objects containing the standardised file URLs and text content.\n    func getFileContent(from filePaths: [URL]) async -> [SearchIndexer.AsyncManager.TextFile] {\n        var textFiles = [SearchIndexer.AsyncManager.TextFile]()\n        for file in filePaths {\n            if let content = try? String(contentsOf: file) {\n                textFiles.append(\n                    SearchIndexer.AsyncManager.TextFile(url: file.standardizedFileURL, text: content)\n                )\n            }\n        }\n        return textFiles\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument+Listeners.swift",
    "content": "//\n//  WorkspaceDocument+CommandListeners.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 6/5/22.\n//\n\nimport Foundation\nimport Combine\n\nclass WorkspaceNotificationModel: ObservableObject {\n\n    @Published var highlightedFileItem: CEWorkspaceFile?\n\n    init() {\n        highlightedFileItem = nil\n    }\n\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument+SearchState.swift",
    "content": "//\n//  WorkspaceDocument+SearchState.swift\n//  CodeEdit\n//\n//  Created by Tom Ludwig on 16.01.24.\n//\n\nimport Foundation\n\nextension WorkspaceDocument {\n    final class SearchState: ObservableObject {\n        enum IndexStatus: Equatable {\n            case none\n            case indexing(progress: Double)\n            case done\n        }\n\n        enum FindNavigatorStatus: Equatable {\n            case none\n            case searching\n            case replacing\n            case found\n            case replaced(updatedFiles: Int)\n            case failed(errorMessage: String)\n        }\n\n        @Published var searchResult: [SearchResultModel] = []\n        @Published var searchResultsFileCount: Int = 0\n        @Published var searchResultsCount: Int = 0\n        /// Stores the user's input, shown when no files are found, and persists across navigation items.\n        @Published var searchQuery: String = \"\"\n        @Published var replaceText: String = \"\"\n\n        @Published var indexStatus: IndexStatus = .none\n\n        @Published var findNavigatorStatus: FindNavigatorStatus = .none\n\n        @Published var shouldFocusSearchField: Bool = false\n\n        unowned var workspace: WorkspaceDocument\n        var tempSearchResults = [SearchResultModel]()\n        var caseSensitive: Bool = false\n        var indexer: SearchIndexer?\n        var selectedMode: [SearchModeModel] = [\n            .Find,\n            .Text,\n            .Containing\n        ]\n\n        init(_ workspace: WorkspaceDocument) {\n            self.workspace = workspace\n            self.indexer = SearchIndexer.Memory.create()\n            addProjectToIndex()\n        }\n\n        /// Represents the compare options to be used for find and replace.\n        ///\n        /// The `replaceOptions` property is a lazy, computed property that dynamically calculates\n        /// the compare options based on the values of `selectedMode` and `ignoreCase`. It is used\n        /// for controlling string replacement behavior for the find and replace functions.\n        ///\n        /// - Note: This property is implemented as a lazy property in the main class body because\n        /// extensions cannot contain stored properties directly.\n        lazy var replaceOptions: NSString.CompareOptions = {\n            var options: NSString.CompareOptions = []\n\n            if selectedMode.second == .RegularExpression {\n                options.insert(.regularExpression)\n            }\n\n            if !caseSensitive {\n                options.insert(.caseInsensitive)\n            }\n\n            return options\n        }()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument.swift",
    "content": "//\n//  WorkspaceDocument.swift\n//  CodeEdit\n//\n//  Created by Pavel Kasila on 17.03.22.\n//\n\nimport AppKit\nimport SwiftUI\nimport Combine\nimport Foundation\nimport LanguageServerProtocol\n\n@objc(WorkspaceDocument)\nfinal class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate {\n    @Published var sortFoldersOnTop: Bool = true\n    /// A string used to filter the displayed files and folders in the project navigator area based on user input.\n    @Published var navigatorFilter: String = \"\"\n    /// Whether the workspace only shows files with changes.\n    @Published var sourceControlFilter = false\n\n    private var workspaceState: [String: Any] {\n        get {\n            let key = \"workspaceState-\\(self.fileURL?.absoluteString ?? \"\")\"\n            return UserDefaults.standard.object(forKey: key) as? [String: Any] ?? [:]\n        }\n        set {\n            let key = \"workspaceState-\\(self.fileURL?.absoluteString ?? \"\")\"\n            UserDefaults.standard.set(newValue, forKey: key)\n        }\n    }\n\n    var workspaceFileManager: CEWorkspaceFileManager?\n\n    var editorManager: EditorManager? = EditorManager()\n    var statusBarViewModel: StatusBarViewModel? = StatusBarViewModel()\n    var utilityAreaModel: UtilityAreaViewModel? = UtilityAreaViewModel()\n    var searchState: SearchState?\n    var openQuicklyViewModel: OpenQuicklyViewModel?\n    var commandsPaletteState: QuickActionsViewModel?\n    var listenerModel: WorkspaceNotificationModel = .init()\n    var sourceControlManager: SourceControlManager?\n\n    var taskManager: TaskManager?\n    var workspaceSettingsManager: CEWorkspaceSettings?\n    var taskNotificationHandler: TaskNotificationHandler = TaskNotificationHandler()\n\n    var undoRegistration: UndoManagerRegistration = UndoManagerRegistration()\n\n    var notificationPanel = NotificationPanelViewModel()\n    private var cancellables = Set<AnyCancellable>()\n\n    override init() {\n        super.init()\n        notificationPanel.workspace = self\n\n        // Observe changes to notification panel\n        notificationPanel.objectWillChange\n            .receive(on: DispatchQueue.main)\n            .sink { [weak self] _ in\n                self?.objectWillChange.send()\n            }\n            .store(in: &cancellables)\n    }\n\n    deinit {\n        cancellables.forEach { $0.cancel() }\n        NotificationCenter.default.removeObserver(self)\n    }\n\n    func getFromWorkspaceState(_ key: WorkspaceStateKey) -> Any? {\n        return workspaceState[key.rawValue]\n    }\n\n    func addToWorkspaceState(key: WorkspaceStateKey, value: Any?) {\n        if let value {\n            workspaceState.updateValue(value, forKey: key.rawValue)\n        } else {\n            workspaceState.removeValue(forKey: key.rawValue)\n        }\n    }\n\n    // MARK: NSDocument\n\n    private let ignoredFilesAndDirectory = [\n        \".DS_Store\"\n    ]\n\n    override static var autosavesInPlace: Bool {\n        false\n    }\n\n    override var isDocumentEdited: Bool {\n        false\n    }\n\n    override func makeWindowControllers() {\n        let window = NSWindow(\n            contentRect: NSRect(x: 0, y: 0, width: 1400, height: 900),\n            styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],\n            backing: .buffered,\n            defer: false\n        )\n        // Note For anyone hoping to switch back to a Root-SwiftUI window:\n        // See Commit 0200c87 for more details and to see what was previously here.\n        // -----\n        // Setting the \"min size\" like this is hacky, but SwiftUI overrides the contentRect and\n        // any of the built-in window size functions & autosave stuff. So we have to set it like this.\n        // SwiftUI also ignores this value, so it just manages to set the initial window size. *Hopefully* this\n        // is fixed in the future.\n        // ----\n        let windowController = CodeEditWindowController(\n            window: window,\n            workspace: self\n        )\n\n        if let rectString = getFromWorkspaceState(.workspaceWindowSize) as? String {\n            window.setFrame(NSRectFromString(rectString), display: true, animate: false)\n        } else {\n            window.setFrame(NSRect(x: 0, y: 0, width: 1400, height: 900), display: true, animate: false)\n            window.center()\n        }\n\n        window.setAccessibilityIdentifier(\"workspace\")\n        window.setAccessibilityDocument(self.fileURL?.absoluteString)\n\n        self.addWindowController(windowController)\n\n        window.makeKeyAndOrderFront(nil)\n    }\n\n    // MARK: Set Up Workspace\n\n    private func initWorkspaceState(_ url: URL) throws {\n        // Ensure the URL ends with a \"/\" to prevent certain URL(filePath:relativeTo) initializers from\n        // placing the file one directory above our workspace. This quick fix appends a \"/\" if needed.\n        var url = url\n        if !url.absoluteString.hasSuffix(\"/\") {\n            url = URL(filePath: url.absoluteURL.path(percentEncoded: false) + \"/\")\n        }\n\n        self.fileURL = url\n        self.displayName = url.lastPathComponent\n\n        let sourceControlManager = SourceControlManager(\n            workspaceURL: url,\n            editorManager: editorManager!\n        )\n\n        self.workspaceFileManager = .init(\n            folderUrl: url,\n            ignoredFilesAndFolders: Set(ignoredFilesAndDirectory),\n            sourceControlManager: sourceControlManager\n        )\n        self.sourceControlManager = sourceControlManager\n        sourceControlManager.fileManager = workspaceFileManager\n        self.searchState = .init(self)\n        self.openQuicklyViewModel = .init(fileURL: url)\n        self.commandsPaletteState = .init()\n        self.workspaceSettingsManager = CEWorkspaceSettings(workspaceURL: url)\n        if let workspaceSettingsManager {\n            self.taskManager = TaskManager(\n                workspaceSettings: workspaceSettingsManager.settings,\n                workspaceURL: url\n            )\n        }\n        self.taskNotificationHandler.workspaceURL = url\n\n        workspaceFileManager?.addObserver(undoRegistration)\n        editorManager?.restoreFromState(self)\n        utilityAreaModel?.restoreFromState(self)\n    }\n\n    override func read(from url: URL, ofType typeName: String) throws {\n        try initWorkspaceState(url)\n    }\n\n    override func write(to url: URL, ofType typeName: String) throws {}\n\n    // MARK: Close Workspace\n\n    override func close() {\n        super.close()\n        editorManager?.saveRestorationState(self)\n        utilityAreaModel?.saveRestorationState(self)\n\n        cancellables.forEach({ $0.cancel() })\n        statusBarViewModel = nil\n        utilityAreaModel = nil\n        searchState = nil\n        editorManager = nil\n        openQuicklyViewModel = nil\n        commandsPaletteState = nil\n        sourceControlManager = nil\n        workspaceFileManager?.cleanUp()\n        workspaceFileManager = nil\n        workspaceSettingsManager?.cleanUp()\n        workspaceSettingsManager = nil\n        taskManager = nil\n    }\n\n    /// Determines the windows should be closed.\n    ///\n    /// This method iterates all edited documents If there are any edited documents.\n    ///\n    /// A panel giving the user the choice of canceling, discarding changes, or saving is presented while iteration.\n    ///\n    /// If the user chooses cancel on the panel, iteration is broken.\n    ///\n    /// In the last step, `shouldCloseSelector` is called with true if all documents are clean, otherwise false\n    ///\n    /// - Parameters:\n    ///   - windowController: The windowController may be closed.\n    ///   - delegate: The object which is a target of `shouldCloseSelector`.\n    ///   - shouldClose: The callback which receives result of this method.\n    ///   - contextInfo: The additional info which is not used in this method.\n    override func shouldCloseWindowController(\n        _ windowController: NSWindowController,\n        delegate: Any?,\n        shouldClose shouldCloseSelector: Selector?,\n        contextInfo: UnsafeMutableRawPointer?\n    ) {\n        guard let object = (delegate as? NSObject),\n              let shouldCloseSelector = shouldCloseSelector,\n              let contextInfo = contextInfo\n        else {\n            super.shouldCloseWindowController(\n                windowController,\n                delegate: delegate,\n                shouldClose: shouldCloseSelector,\n                contextInfo: contextInfo\n            )\n            return\n        }\n        // Save unsaved changes before closing\n        let editedCodeFiles = editorManager?.editorLayout\n            .gatherOpenFiles()\n            .compactMap(\\.fileDocument)\n            .filter(\\.isDocumentEdited) ?? []\n\n        for editedCodeFile in editedCodeFiles {\n            let shouldClose = UnsafeMutablePointer<Bool>.allocate(capacity: 1)\n            shouldClose.initialize(to: true)\n            defer {\n                _ = shouldClose.move()\n                shouldClose.deallocate()\n            }\n            // Present a panel giving the user the choice of canceling, discarding changes, or saving.\n            editedCodeFile.canClose(\n                withDelegate: self,\n                shouldClose: #selector(document(_:shouldClose:contextInfo:)),\n                contextInfo: shouldClose\n            )\n            // pointee becomes false when user select cancel\n            guard shouldClose.pointee else {\n                break\n            }\n        }\n        // Invoke shouldCloseSelector at delegate\n        let implementation = object.method(for: shouldCloseSelector)\n        let function = unsafeBitCast(\n            implementation,\n            to: (@convention(c)(Any, Selector, Any, Bool, UnsafeMutableRawPointer?) -> Void).self\n        )\n        let areAllOpenedCodeFilesClean = editorManager?.editorLayout.gatherOpenFiles()\n            .compactMap(\\.fileDocument)\n            .allSatisfy { !$0.isDocumentEdited } ?? false\n        function(object, shouldCloseSelector, self, areAllOpenedCodeFilesClean, contextInfo)\n    }\n\n    // MARK: NSDocument delegate\n\n    /// Receives result of `canClose` and then, set `shouldClose` to `contextInfo`'s `pointee`.\n    ///\n    /// - Parameters:\n    ///   - document: The document may be closed.\n    ///   - shouldClose: The result of user selection.\n    ///      `shouldClose` becomes false if the user selects cancel, otherwise true.\n    ///   - contextInfo: The additional info which will be set `shouldClose`.\n    ///       `contextInfo` must be `UnsafeMutablePointer<Bool>`.\n    @objc\n    func document(\n        _ document: NSDocument,\n        shouldClose: Bool,\n        contextInfo: UnsafeMutableRawPointer\n    ) {\n        let opaquePtr = OpaquePointer(contextInfo)\n        let mutablePointer = UnsafeMutablePointer<Bool>(opaquePtr)\n        mutablePointer.pointee = shouldClose\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceStateKey.swift",
    "content": "//\n//  WorkspaceStateKey.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/3/23.\n//\n\nenum WorkspaceStateKey: String {\n    case utilityAreaCollapsed\n    case utilityAreaMaximized\n    case utilityAreaHeight\n    case openTabs\n    case workspaceWindowSize\n    case splitViewWidth\n    case navigatorCollapsed\n    case inspectorCollapsed\n    case toolbarCollapsed\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/JumpBar/Views/EditorJumpBarComponent.swift",
    "content": "//\n//  EditorJumpBar.swift\n//  CodeEdit\n//\n//  Created by Lukas Pistrol on 18.03.22.\n//\n\nimport SwiftUI\nimport Combine\nimport CodeEditSymbols\n\nstruct EditorJumpBarComponent: View {\n    private let fileItem: CEWorkspaceFile\n    private let tappedOpenFile: (CEWorkspaceFile) -> Void\n    private let isLastItem: Bool\n\n    @Environment(\\.colorScheme)\n    var colorScheme\n\n    @Environment(\\.controlActiveState)\n    private var activeState\n\n    @EnvironmentObject var workspace: WorkspaceDocument\n\n    @State var position: NSPoint?\n    @State var selection: CEWorkspaceFile\n    @State var isHovering: Bool = false\n    @State var button = NSPopUpButton()\n    @Binding var truncatedCrumbWidth: CGFloat?\n\n    init(\n        fileItem: CEWorkspaceFile,\n        tappedOpenFile: @escaping (CEWorkspaceFile) -> Void,\n        isLastItem: Bool,\n        isTruncated: Binding<CGFloat?>\n    ) {\n        self.fileItem = fileItem\n        self._selection = .init(wrappedValue: fileItem)\n        self.tappedOpenFile = tappedOpenFile\n        self.isLastItem = isLastItem\n        self._truncatedCrumbWidth = isTruncated\n    }\n\n    var siblings: [CEWorkspaceFile] {\n        guard let fileManager = workspace.workspaceFileManager,\n              let parent = fileItem.parent else {\n            return [fileItem]\n        }\n        if let siblings = fileManager.childrenOfFile(parent), !siblings.isEmpty {\n            return siblings\n        } else {\n            return [fileItem]\n        }\n    }\n\n    var body: some View {\n        NSPopUpButtonView(selection: $selection) {\n            guard let fileManager = workspace.workspaceFileManager else { return NSPopUpButton() }\n\n            button.menu = EditorJumpBarMenu(\n                fileItems: siblings,\n                fileManager: fileManager,\n                tappedOpenFile: tappedOpenFile\n            )\n            button.font = .systemFont(ofSize: NSFont.systemFontSize(for: .small))\n            button.isBordered = false\n            (button.cell as? NSPopUpButtonCell)?.arrowPosition = .noArrow\n\n            return button\n        }\n        .frame(\n            maxWidth: isHovering || isLastItem ? nil : truncatedCrumbWidth,\n            alignment: .leading\n        )\n        .mask(\n            LinearGradient(\n                gradient: Gradient(\n                    stops: truncatedCrumbWidth == nil || isHovering ?\n                    [\n                        .init(color: .black, location: 0),\n                        .init(color: .black, location: 1)\n                    ] : [\n                        .init(color: .black, location: 0),\n                        .init(color: .black, location: 0.8),\n                        .init(color: .clear, location: 1)\n                    ]\n                ),\n                startPoint: .leading,\n                endPoint: .trailing\n            )\n        )\n        .clipped()\n        .padding(.trailing, 11)\n        .background {\n            Color(nsColor: colorScheme == .dark ? .white : .black)\n                .opacity(isHovering ? 0.05 : 0)\n                .clipShape(RoundedRectangle(cornerSize: CGSize(width: 4, height: 4)))\n            HStack {\n                Spacer()\n                if isHovering {\n                    chevronUpDown\n                        .padding(.trailing, 4)\n                } else if !isLastItem {\n                    chevron\n                        .padding(.trailing, 3)\n                }\n            }\n        }\n        .padding(.vertical, 3)\n        .onHover { hover in\n            withAnimation(.easeInOut(duration: 0.2)) {\n                isHovering = hover\n            }\n        }\n        .onLongPressGesture(minimumDuration: 0) {\n            button.performClick(nil)\n        }\n        .opacity(activeState != .inactive ? 1 : 0.75)\n    }\n\n    private var chevron: some View {\n        Image(systemName: \"chevron.compact.right\")\n            .font(.system(size: 9, weight: activeState != .inactive ? .medium : .bold, design: .default))\n            .foregroundStyle(.secondary)\n            .scaleEffect(x: 1.30, y: 1.0, anchor: .center)\n            .imageScale(.large)\n    }\n\n    private var chevronUpDown: some View {\n        VStack(spacing: 1) {\n            Image(systemName: \"chevron.up\")\n            Image(systemName: \"chevron.down\")\n        }\n        .font(.system(size: 6, weight: .bold, design: .default))\n        .padding(.top, 0.5)\n    }\n\n    struct NSPopUpButtonView<ItemType>: NSViewRepresentable where ItemType: Equatable {\n        @Binding var selection: ItemType\n\n        var popupCreator: () -> NSPopUpButton\n\n        typealias NSViewType = NSPopUpButton\n\n        func makeNSView(context: NSViewRepresentableContext<NSPopUpButtonView>) -> NSPopUpButton {\n            let newPopupButton = popupCreator()\n            setPopUpFromSelection(newPopupButton, selection: selection)\n            if let menu = newPopupButton.menu {\n                context.coordinator.registerForChanges(in: menu)\n            }\n            return newPopupButton\n        }\n\n        func updateNSView(_ nsView: NSPopUpButton, context: NSViewRepresentableContext<NSPopUpButtonView>) {\n            setPopUpFromSelection(nsView, selection: selection)\n        }\n\n        func setPopUpFromSelection(_ button: NSPopUpButton, selection: ItemType) {\n            let itemsList = button.itemArray\n            let matchedMenuItem = itemsList.filter {\n                ($0.representedObject as? ItemType) == selection\n            }.first\n            if matchedMenuItem != nil {\n                button.select(matchedMenuItem)\n            }\n        }\n\n        func makeCoordinator() -> Coordinator {\n            return Coordinator(self)\n        }\n\n        class Coordinator: NSObject {\n            var parent: NSPopUpButtonView\n\n            var cancellable: AnyCancellable?\n\n            init(_ parent: NSPopUpButtonView) {\n                self.parent = parent\n                super.init()\n            }\n\n            func registerForChanges(in menu: NSMenu) {\n                cancellable = NotificationCenter.default\n                    .publisher(for: NSMenu.didSendActionNotification, object: menu)\n                    .sink { [weak self] notification in\n                        if let menuItem = notification.userInfo?[\"MenuItem\"] as? NSMenuItem,\n                           let selection = menuItem as? ItemType {\n                            self?.parent.selection = selection\n                        }\n                    }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/JumpBar/Views/EditorJumpBarMenu.swift",
    "content": "//\n//  EditorJumpBarMenu.swift\n//  CodeEdit\n//\n//  Created by Ziyuan Zhao on 2022/3/29.\n//\n\nimport AppKit\n\nfinal class EditorJumpBarMenu: NSMenu, NSMenuDelegate {\n    private let fileItems: [CEWorkspaceFile]\n    private weak var fileManager: CEWorkspaceFileManager?\n    private let tappedOpenFile: (CEWorkspaceFile) -> Void\n\n    init(\n        fileItems: [CEWorkspaceFile],\n        fileManager: CEWorkspaceFileManager,\n        tappedOpenFile: @escaping (CEWorkspaceFile) -> Void\n    ) {\n        self.fileItems = fileItems\n        self.fileManager = fileManager\n        self.tappedOpenFile = tappedOpenFile\n        super.init(title: \"\")\n        delegate = self\n        fileItems.forEach { item in\n            let menuItem = JumpBarMenuItem(fileItem: item, tappedOpenFile: tappedOpenFile)\n            menuItem.onStateImage = nil\n            self.addItem(menuItem)\n        }\n        autoenablesItems = false\n    }\n\n    @available(*, unavailable)\n    required init(coder _: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n    /// Only when menu item is highlighted then generate its submenu\n    func menu(_ menu: NSMenu, willHighlight item: NSMenuItem?) {\n        if let highlightedItem = item, let submenuItems = highlightedItem.submenu?.items, submenuItems.isEmpty {\n            if let highlightedFileItem = highlightedItem.representedObject as? CEWorkspaceFile {\n                highlightedItem.submenu = generateSubmenu(highlightedFileItem)\n            }\n        }\n    }\n\n    private func generateSubmenu(_ fileItem: CEWorkspaceFile) -> EditorJumpBarMenu? {\n        if let fileManager = fileManager,\n           let children = fileManager.childrenOfFile(fileItem) {\n            let menu = EditorJumpBarMenu(\n                fileItems: children,\n                fileManager: fileManager,\n                tappedOpenFile: tappedOpenFile\n            )\n            return menu\n        }\n        return nil\n    }\n}\n\nfinal class JumpBarMenuItem: NSMenuItem {\n    private let fileItem: CEWorkspaceFile\n    private let tappedOpenFile: (CEWorkspaceFile) -> Void\n    private let generalSettings = Settings.shared.preferences.general\n\n    init(\n        fileItem: CEWorkspaceFile,\n        tappedOpenFile: @escaping (CEWorkspaceFile) -> Void\n    ) {\n        self.fileItem = fileItem\n        self.tappedOpenFile = tappedOpenFile\n        super.init(title: fileItem.name, action: #selector(openFile), keyEquivalent: \"\")\n        var color = NSColor(fileItem.iconColor)\n        isEnabled = true\n        target = self\n        if fileItem.isFolder {\n            let subMenu = NSMenu()\n            submenu = subMenu\n            color = NSColor.folderBlue\n        }\n        if generalSettings.fileIconStyle == .monochrome {\n            color = NSColor.coolGray\n        }\n        let image = fileItem.nsIcon.withSymbolConfiguration(.init(paletteColors: [color]))\n        self.image = image\n        representedObject = fileItem\n        if fileItem.isFolder {\n            self.action = nil\n        }\n    }\n\n    @available(*, unavailable)\n    required init(coder _: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n    @objc\n    func openFile() {\n        tappedOpenFile(fileItem)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/JumpBar/Views/EditorJumpBarView.swift",
    "content": "//\n//  EditorJumpBarView.swift\n//  CodeEdit\n//\n//  Created by Lukas Pistrol on 17.03.22.\n//\n\nimport SwiftUI\n\nstruct EditorJumpBarView: View {\n    private let file: CEWorkspaceFile?\n    private let shouldShowTabBar: Bool\n    private let tappedOpenFile: (CEWorkspaceFile) -> Void\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @Environment(\\.isActiveEditor)\n    private var isActiveEditor\n\n    @Environment(\\.controlActiveState)\n    private var activeState\n\n    @Binding var codeFile: CodeFileDocument?\n\n    static let height = 28.0\n\n    @State private var textWidth: CGFloat = 0\n    @State private var containerWidth: CGFloat = 0\n    @State private var isTruncated: Bool = false\n    @State private var crumbWidth: CGFloat?\n    @State private var firstCrumbWidth: CGFloat?\n\n    init(\n        file: CEWorkspaceFile?,\n        shouldShowTabBar: Bool,\n        codeFile: Binding<CodeFileDocument?>,\n        tappedOpenFile: @escaping (CEWorkspaceFile) -> Void\n    ) {\n        self.file = file ?? nil\n        self.shouldShowTabBar = shouldShowTabBar\n        self._codeFile = codeFile\n        self.tappedOpenFile = tappedOpenFile\n    }\n\n    var fileItems: [CEWorkspaceFile] {\n        var treePath: [CEWorkspaceFile] = []\n        var currentFile: CEWorkspaceFile? = file\n\n        while let currentFileLoop = currentFile {\n            treePath.insert(currentFileLoop, at: 0)\n            currentFile = currentFileLoop.parent\n        }\n\n        return treePath\n    }\n\n    var body: some View {\n        GeometryReader { containerProxy in\n            ScrollView(.horizontal, showsIndicators: false) {\n                HStack(spacing: 0) {\n                    if file == nil {\n                        Text(\"No Selection\")\n                            .font(.system(size: 11, weight: .regular))\n                            .foregroundColor(\n                                activeState != .inactive\n                                ? isActiveEditor ? .primary : .secondary\n                                : Color(nsColor: .tertiaryLabelColor)\n                            )\n                            .frame(maxHeight: .infinity)\n                    } else {\n                        ForEach(fileItems, id: \\.self) { fileItem in\n                            EditorJumpBarComponent(\n                                fileItem: fileItem,\n                                tappedOpenFile: tappedOpenFile,\n                                isLastItem: fileItems.last == fileItem,\n                                isTruncated: fileItems.first == fileItem ? $firstCrumbWidth : $crumbWidth\n                            )\n                        }\n\n                    }\n                }\n                .background(\n                    GeometryReader { proxy in\n                        Color.clear\n                            .onAppear {\n                                if crumbWidth == nil {\n                                    textWidth = proxy.size.width\n                                }\n                            }\n                            .onChange(of: proxy.size.width) { _, newValue in\n                                if crumbWidth == nil {\n                                    textWidth = newValue\n                                }\n                            }\n                    }\n                )\n            }\n            .onAppear {\n                containerWidth = containerProxy.size.width\n            }\n            .onChange(of: containerProxy.size.width) { _, newValue in\n                containerWidth = newValue\n            }\n            .onChange(of: textWidth) { _, _ in\n                withAnimation(.easeInOut(duration: 0.2)) {\n                    resize()\n                }\n            }\n            .onChange(of: containerWidth) { _, _ in\n                withAnimation(.easeInOut(duration: 0.2)) {\n                    resize()\n                }\n            }\n        }\n        .padding(.horizontal, shouldShowTabBar ? (file == nil ? 10 : 4) : 0)\n        .safeAreaInset(edge: .leading, spacing: 0) {\n            if !shouldShowTabBar {\n                EditorTabBarLeadingAccessories()\n            }\n        }\n        .safeAreaInset(edge: .trailing, spacing: 0) {\n            if !shouldShowTabBar {\n                EditorTabBarTrailingAccessories(codeFile: $codeFile)\n            }\n        }\n        .frame(height: Self.height, alignment: .center)\n        .opacity(activeState == .inactive ? 0.8 : 1.0)\n        .grayscale(isActiveEditor ? 0.0 : 1.0)\n    }\n\n    private func resize() {\n        let minWidth: CGFloat = 20\n        let snapThreshold: CGFloat = 30\n        let maxWidth: CGFloat = textWidth / CGFloat(fileItems.count)\n        let exponent: CGFloat = 5.0\n        var betweenWidth: CGFloat = 0.0\n\n        if textWidth >= containerWidth {\n            let scale = max(0, min(1, containerWidth / textWidth))\n            betweenWidth = floor((minWidth + (maxWidth - minWidth) * pow(scale, exponent)))\n            if betweenWidth < snapThreshold {\n                betweenWidth = minWidth\n            }\n            crumbWidth = betweenWidth\n        } else {\n            crumbWidth = nil\n        }\n\n        if betweenWidth > snapThreshold || crumbWidth == nil {\n            firstCrumbWidth = nil\n        } else {\n            let otherCrumbs = CGFloat(max(fileItems.count - 1, 1))\n            let usedWidth = otherCrumbs * snapThreshold\n\n            // Multiplier to reserve extra space for other crumbs in the jump bar.\n            // Increasing this value causes the first crumb to truncate sooner.\n            let crumbSpacingMultiplier: CGFloat = 1.5\n            let availableForFirst = containerWidth - usedWidth * crumbSpacingMultiplier\n            if availableForFirst < snapThreshold {\n                firstCrumbWidth = minWidth\n            } else {\n                firstCrumbWidth = availableForFirst\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/Models/Editor/Editor+History.swift",
    "content": "//\n//  Editor+History.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/9/24.\n//\n\nimport Foundation\n\n/// Methods for modifying the history list on the editor.\nextension Editor {\n    /// Add the tab to the history list.\n    /// - Parameter tab: The tab to add to the history.\n    func addToHistory(_ tab: Tab) {\n        if history.first != tab.file {\n            history.prepend(tab.file)\n        }\n    }\n\n    /// Clear any tabs in the \"future\" on the history list. Resets the history offset and removes any tabs that were\n    /// available to navigate forwards to.\n    func clearFuture() {\n        guard historyOffset > 0 else { return } // nothing to clear, avoid an out of bounds error\n        history.removeFirst(historyOffset)\n        historyOffset = 0\n    }\n\n    /// Move backwards in the history list by one place.\n    func goBackInHistory() {\n        if canGoBackInHistory {\n            historyOffset += 1\n        }\n    }\n\n    /// Move forwards in the history list by one place.\n    func goForwardInHistory() {\n        if canGoForwardInHistory {\n            historyOffset -= 1\n        }\n    }\n\n    // TODO: move to @Observable so this works better\n    /// Warning: NOT published!\n    var canGoBackInHistory: Bool {\n        historyOffset != history.count - 1 && !history.isEmpty\n    }\n\n    // TODO: move to @Observable so this works better\n    /// Warning: NOT published!\n    var canGoForwardInHistory: Bool {\n        historyOffset != 0\n    }\n\n    /// Called by the ``Editor`` class when the history offset is changed.\n    ///\n    /// This method updates the selected tab to the current tab in the history offset.\n    /// If the tab is not opened, it is opened without modifying the history list.\n    /// - Warning: Do not use except in the ``historyOffset``'s `didSet`.\n    func historyOffsetDidChange() {\n        let file = history[historyOffset]\n\n        if !tabs.contains(where: { $0.file == file }) {\n            if let temporaryTab, tabs.contains(temporaryTab) {\n                closeTab(file: temporaryTab.file, fromHistory: true)\n            }\n            openTab(file: file, fromHistory: true)\n            if let tab = tabs.first(where: { $0.file.id == file.id }) {\n                temporaryTab = tab\n            }\n        }\n        setSelectedTab(file)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/Models/Editor/Editor+TabSwitch.swift",
    "content": "//\n//  EditorTabSwitchExtension.swift\n//  CodeEdit\n//\n//  Created by Roscoe Rubin-Rottenberg on 4/22/24.\n//\n\nimport Foundation\n\nextension Editor {\n    func selectNextTab() {\n        guard let currentTab = selectedTab, let currentIndex = tabs.firstIndex(of: currentTab) else { return }\n        let nextIndex = tabs.index(after: currentIndex)\n        if nextIndex < tabs.endIndex {\n            setSelectedTab(tabs[nextIndex].file)\n        } else {\n            // Wrap around to the first tab if it's the last one\n            setSelectedTab(tabs.first?.file)\n        }\n    }\n\n    func selectPreviousTab() {\n        guard let currentTab = selectedTab, let currentIndex = tabs.firstIndex(of: currentTab) else { return }\n        let previousIndex = tabs.index(before: currentIndex)\n        if previousIndex >= tabs.startIndex {\n            setSelectedTab(tabs[previousIndex].file)\n        } else {\n            // Wrap around to the last tab if it's the first one\n            setSelectedTab(tabs.last?.file)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/Models/Editor/Editor.swift",
    "content": "//\n//  Editor.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 16/02/2023.\n//\n\nimport Foundation\nimport OrderedCollections\nimport DequeModule\nimport AppKit\nimport OSLog\n\nfinal class Editor: ObservableObject, Identifiable {\n    enum EditorError: Error {\n        case noWorkspaceAttached\n    }\n\n    typealias Tab = EditorInstance\n\n    /// Set of open tabs.\n    @Published var tabs: OrderedSet<Tab> = [] {\n        didSet {\n            let change = tabs.symmetricDifference(oldValue)\n\n            if tabs.count > oldValue.count {\n                // Amount of tabs grew, so set the first new as selected.\n                setSelectedTab(change.first?.file)\n            } else {\n                // Selected file was removed\n                if let selectedTab, change.contains(selectedTab) {\n                    if let oldIndex = oldValue.firstIndex(of: selectedTab), oldIndex - 1 < tabs.count, !tabs.isEmpty {\n                        setSelectedTab(tabs[max(0, oldIndex-1)].file)\n                    } else {\n                        setSelectedTab(nil)\n                    }\n                }\n            }\n        }\n    }\n\n    /// The current offset in the history list.\n    /// When set, updates the ``selectedTab`` to the tab indicated by the offset.\n    /// See the ``historyOffsetDidChange()`` method for more details.\n    @Published var historyOffset: Int = 0 {\n        didSet {\n            historyOffsetDidChange()\n        }\n    }\n\n    /// Maintains the list of tabs that have been switched to.\n    /// - Warning: Use the ``addToHistory(_:)`` or ``clearFuture()`` methods to modify this. Do not modify directly.\n    @Published var history: Deque<CEWorkspaceFile> = []\n\n    /// Currently selected tab.\n    @Published private(set) var selectedTab: Tab?\n\n    @Published var temporaryTab: Tab?\n\n    var id = UUID()\n\n    weak var parent: SplitViewData?\n    weak var workspace: WorkspaceDocument?\n\n    private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? \"\", category: \"Editor\")\n\n    init() {\n        self.tabs = []\n        self.temporaryTab = nil\n        self.parent = nil\n        self.workspace = nil\n    }\n\n    init(\n        files: OrderedSet<CEWorkspaceFile> = [],\n        selectedTab: Tab? = nil,\n        temporaryTab: Tab? = nil,\n        parent: SplitViewData? = nil,\n        workspace: WorkspaceDocument? = nil\n    ) {\n        self.parent = parent\n        self.workspace = workspace\n        // If we open the files without a valid workspace, we risk creating a file we lose track of but stays in memory\n        if workspace != nil {\n            files.forEach { openTab(file: $0) }\n        } else {\n            self.tabs = OrderedSet(files.map { EditorInstance(workspace: workspace, file: $0) })\n        }\n        self.selectedTab = selectedTab ?? (files.isEmpty ? nil : Tab(workspace: workspace, file: files.first!))\n        self.temporaryTab = temporaryTab\n    }\n\n    init(\n        files: OrderedSet<Tab> = [],\n        selectedTab: Tab? = nil,\n        temporaryTab: Tab? = nil,\n        parent: SplitViewData? = nil,\n        workspace: WorkspaceDocument? = nil\n    ) {\n        self.tabs = []\n        self.parent = parent\n        self.workspace = workspace\n        files.forEach { openTab(file: $0.file) }\n        self.selectedTab = selectedTab ?? tabs.first\n        self.temporaryTab = temporaryTab\n    }\n\n    /// Closes the editor.\n    func close() {\n        parent?.closeEditor(with: id)\n    }\n\n    /// Gets the editor layout.\n    func getEditorLayout() -> EditorLayout? {\n        return parent?.getEditorLayout(with: id)\n    }\n\n    /// Set the selected tab. Loads the file's contents if it hasn't already been opened.\n    /// - Parameter file: The file to set as the selected tab.\n    func setSelectedTab(_ file: CEWorkspaceFile?) {\n        guard let file else {\n            selectedTab = nil\n            return\n        }\n        guard let tab = self.tabs.first(where: { $0.file == file }) else {\n            return\n        }\n        self.selectedTab = tab\n        if tab.file.fileDocument == nil {\n            do { // Ignore this error for simpler API usage.\n                try openFile(item: tab)\n            } catch {\n                print(error)\n            }\n        }\n    }\n\n    /// Closes a tab in the editor.\n    /// This will also write any changes to the file on disk and will add the tab to the tab history.\n    /// - Parameters:\n    ///   - file: The tab to close\n    ///   - fromHistory: If `true`, does not clear tabs ahead of the ``historyOffset``\n    ///                  Used when opening tabs from the history queue where tabs ahead of the ``historyOffset`` should\n    ///                  not be removed.\n    func closeTab(file: CEWorkspaceFile, fromHistory: Bool = false) {\n        guard canCloseTab(file: file) else { return }\n\n        if temporaryTab?.file == file {\n            temporaryTab = nil\n        }\n        if !fromHistory {\n            clearFuture()\n        }\n        if file != selectedTab?.file {\n            addToHistory(EditorInstance(workspace: workspace, file: file))\n        }\n        removeTab(file)\n        if let selectedTab {\n            addToHistory(selectedTab)\n        }\n        // Reset change count to 0\n        file.fileDocument?.updateChangeCount(.changeCleared)\n        if let codeFile = file.fileDocument {\n            codeFile.close()\n        }\n        // remove file from memory\n        file.fileDocument = nil\n    }\n\n    /// Closes the currently opened tab in the tab group.\n    func closeSelectedTab() {\n        guard let file = selectedTab?.file else {\n            return\n        }\n\n        closeTab(file: file)\n    }\n\n    /// Opens a tab in the editor.\n    /// If a tab for the item already exists, it is used instead.\n    /// - Parameters:\n    ///   - file: the file to open.\n    ///   - asTemporary: indicates whether the tab should be opened as a temporary tab or a permanent tab.\n    func openTab(file: CEWorkspaceFile, asTemporary: Bool) {\n        let item = EditorInstance(workspace: workspace, file: file)\n        // Item is already opened in a tab.\n        guard !tabs.contains(item) || !asTemporary else {\n            selectedTab = item\n            addToHistory(item)\n            return\n        }\n\n        switch (temporaryTab, asTemporary) {\n        case (.some(let tab), true):\n            replaceTemporaryTab(tab: tab, with: item)\n        case (.some(let tab), false) where tab == item:\n            temporaryTab = nil\n        case (.some(let tab), false) where tab != item:\n            openTab(file: item.file)\n        case (.some, false):\n            // A temporary tab exists, but we don't want to open this one as temporary.\n            // Clear the temp tab and open the new one.\n            openTab(file: item.file)\n        case (.none, true):\n            openTab(file: item.file)\n            temporaryTab = item\n        case (.none, false):\n            openTab(file: item.file)\n        }\n    }\n\n    /// Replaces the given temporary tab with a new tab item.\n    /// - Parameters:\n    ///   - tab: The temporary tab to replace.\n    ///   - newItem: The new tab to replace it with and open as a temporary tab.\n    private func replaceTemporaryTab(tab: Tab, with newItem: Tab) {\n        if let index = tabs.firstIndex(of: tab) {\n            do {\n                try openFile(item: newItem)\n            } catch {\n                logger.error(\"Error opening file: \\(error)\")\n            }\n\n            clearFuture()\n            addToHistory(newItem)\n            tabs.remove(tab)\n            tabs.insert(newItem, at: index)\n            self.selectedTab = newItem\n            temporaryTab = newItem\n        } else {\n            // If we couldn't find the current temporary tab (invalid state) we should still do *something*\n            openTab(file: newItem.file)\n            temporaryTab = newItem\n        }\n    }\n\n    /// Opens a tab in the editor.\n    /// - Parameters:\n    ///   - file: The tab to open.\n    ///   - index: Index where the tab needs to be added. If nil, it is added to the back.\n    ///   - fromHistory: Indicates whether the tab has been opened from going back in history.\n    func openTab(file: CEWorkspaceFile, at index: Int? = nil, fromHistory: Bool = false) {\n        let item = Tab(workspace: workspace, file: file)\n        if let index {\n            tabs.insert(item, at: index)\n        } else {\n            if let selectedTab, let currentIndex = tabs.firstIndex(of: selectedTab) {\n                tabs.insert(item, at: tabs.index(after: currentIndex))\n            } else {\n                tabs.append(item)\n            }\n        }\n\n        selectedTab = item\n        if !fromHistory {\n            clearFuture()\n            addToHistory(item)\n        }\n        do {\n            try openFile(item: item)\n        } catch {\n            logger.error(\"Error opening file: \\(error)\")\n        }\n    }\n\n    private func openFile(item: Tab) throws {\n        // If this isn't attached to a workspace, loading a new NSDocument will cause a loose document we can't close\n        guard item.file.fileDocument == nil else {\n            return\n        }\n\n        guard workspace != nil else {\n            throw EditorError.noWorkspaceAttached\n        }\n\n        try item.file.loadCodeFile()\n    }\n\n    /// Check if tab can be closed\n    ///\n    /// If document edited it will show dialog where user can save document before closing or cancel.\n    private func canCloseTab(file: CEWorkspaceFile) -> Bool {\n        guard let codeFile = file.fileDocument else { return true }\n\n        if codeFile.isDocumentEdited {\n            let shouldClose = UnsafeMutablePointer<Bool>.allocate(capacity: 1)\n            shouldClose.initialize(to: true)\n            defer {\n                _ = shouldClose.move()\n                shouldClose.deallocate()\n            }\n            codeFile.canClose(\n                withDelegate: self,\n                shouldClose: #selector(document(_:shouldClose:contextInfo:)),\n                contextInfo: shouldClose\n            )\n\n            return shouldClose.pointee\n        }\n\n        return true\n    }\n\n    /// Receives result of `canClose` and then, set `shouldClose` to `contextInfo`'s `pointee`.\n    ///\n    /// - Parameters:\n    ///   - document: The document may be closed.\n    ///   - shouldClose: The result of user selection.\n    ///      `shouldClose` becomes false if the user selects cancel, otherwise true.\n    ///   - contextInfo: The additional info which will be set `shouldClose`.\n    ///       `contextInfo` must be `UnsafeMutablePointer<Bool>`.\n    @objc\n    func document(\n        _ document: NSDocument,\n        shouldClose: Bool,\n        contextInfo: UnsafeMutableRawPointer\n    ) {\n        let opaquePtr = OpaquePointer(contextInfo)\n        let mutablePointer = UnsafeMutablePointer<Bool>(opaquePtr)\n        mutablePointer.pointee = shouldClose\n    }\n\n    /// Remove the given file from tabs.\n    /// - Parameter file: The file to remove.\n    func removeTab(_ file: CEWorkspaceFile) {\n        tabs.removeAll(where: { tab in tab.file == file })\n        if temporaryTab?.file == file {\n            temporaryTab = nil\n        }\n    }\n}\n\nextension Editor: Equatable, Hashable {\n    static func == (lhs: Editor, rhs: Editor) -> Bool {\n        lhs.id == rhs.id\n    }\n\n    func hash(into hasher: inout Hasher) {\n        hasher.combine(id)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/Models/EditorInstance.swift",
    "content": "//\n//  EditorInstance.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 1/4/24.\n//\n\nimport Foundation\nimport AppKit\nimport Combine\nimport CodeEditTextView\nimport CodeEditSourceEditor\n\n/// A single instance of an editor in a group with a published ``EditorInstance/cursorPositions`` variable to publish\n/// the user's current location in a file.\nclass EditorInstance: ObservableObject, Hashable {\n    /// The file presented in this editor instance.\n    let file: CEWorkspaceFile\n\n    /// A publisher for the user's current location in a file.\n    @Published var cursorPositions: [CursorPosition]\n    @Published var scrollPosition: CGPoint?\n\n    @Published var findText: String?\n    var findTextSubject: PassthroughSubject<String?, Never>\n\n    @Published var replaceText: String?\n    var replaceTextSubject: PassthroughSubject<String?, Never>\n\n    var rangeTranslator: RangeTranslator = RangeTranslator()\n\n    private var cancellables: Set<AnyCancellable> = []\n\n    // MARK: - Init\n\n    init(workspace: WorkspaceDocument?, file: CEWorkspaceFile, cursorPositions: [CursorPosition]? = nil) {\n        self.file = file\n        let url = file.url\n        let editorState = EditorStateRestoration.shared?.restorationState(for: url)\n\n        findText = workspace?.searchState?.searchQuery\n        findTextSubject = PassthroughSubject()\n        replaceText = workspace?.searchState?.replaceText\n        replaceTextSubject = PassthroughSubject()\n\n        self.cursorPositions = (\n            cursorPositions ?? editorState?.editorCursorPositions ?? [CursorPosition(line: 1, column: 1)]\n        )\n        self.scrollPosition = editorState?.scrollPosition\n\n        // Setup listeners\n\n        Publishers.CombineLatest(\n            $cursorPositions.removeDuplicates(),\n            $scrollPosition\n                .debounce(for: .seconds(0.1), scheduler: RunLoop.main) // This can trigger *very* often\n                .removeDuplicates()\n        )\n        .sink { (cursorPositions, scrollPosition) in\n            EditorStateRestoration.shared?.updateRestorationState(\n                for: url,\n                data: .init(cursorPositions: cursorPositions, scrollPosition: scrollPosition ?? .zero)\n            )\n        }\n        .store(in: &cancellables)\n\n        listenToFindText(workspace: workspace)\n        listenToReplaceText(workspace: workspace)\n    }\n\n    // MARK: - Find/Replace Listeners\n\n    func listenToFindText(workspace: WorkspaceDocument?) {\n        workspace?.searchState?.$searchQuery\n            .receive(on: RunLoop.main)\n            .sink { [weak self] newQuery in\n                if self?.findText != newQuery {\n                    self?.findText = newQuery\n                }\n            }\n            .store(in: &cancellables)\n        findTextSubject\n            .receive(on: RunLoop.main)\n            .sink { [weak workspace, weak self] newFindText in\n                if let newFindText, workspace?.searchState?.searchQuery != newFindText {\n                    workspace?.searchState?.searchQuery = newFindText\n                }\n                self?.findText = workspace?.searchState?.searchQuery\n            }\n            .store(in: &cancellables)\n    }\n\n    func listenToReplaceText(workspace: WorkspaceDocument?) {\n        workspace?.searchState?.$replaceText\n            .receive(on: RunLoop.main)\n            .sink { [weak self] newText in\n                if self?.replaceText != newText {\n                    self?.replaceText = newText\n                }\n            }\n            .store(in: &cancellables)\n        replaceTextSubject\n            .receive(on: RunLoop.main)\n            .sink { [weak workspace, weak self] newReplaceText in\n                if let newReplaceText, workspace?.searchState?.replaceText != newReplaceText {\n                    workspace?.searchState?.replaceText = newReplaceText\n                }\n                self?.replaceText = workspace?.searchState?.replaceText\n            }\n            .store(in: &cancellables)\n    }\n\n    // MARK: - Hashable, Equatable\n\n    func hash(into hasher: inout Hasher) {\n        hasher.combine(file)\n    }\n\n    static func == (lhs: EditorInstance, rhs: EditorInstance) -> Bool {\n        lhs.file == rhs.file\n    }\n\n    // MARK: - RangeTranslator\n\n    /// Translates ranges (eg: from a cursor position) to other information like the number of lines in a range.\n    class RangeTranslator: TextViewCoordinator {\n        private weak var textViewController: TextViewController?\n\n        init() { }\n\n        func prepareCoordinator(controller: TextViewController) {\n            self.textViewController = controller\n        }\n\n        func controllerDidAppear(controller: TextViewController) {\n            if controller.isEditable && controller.isSelectable {\n                controller.view.window?.makeFirstResponder(controller.textView)\n            }\n        }\n\n        func destroy() {\n            self.textViewController = nil\n        }\n\n        /// Returns the lines contained in the given range.\n        /// - Parameter range: The range to use.\n        /// - Returns: The number of lines contained by the given range. Or `0` if the text view could not be found,\n        ///            or lines could not be found for the given range.\n        func linesInRange(_ range: NSRange) -> Int {\n            guard let controller = textViewController,\n                  let scrollView = controller.view as? NSScrollView,\n                  let textView = scrollView.documentView as? TextView,\n                  // Find the lines at the beginning and end of the range\n                  let startTextLine = textView.layoutManager.textLineForOffset(range.location),\n                  let endTextLine = textView.layoutManager.textLineForOffset(range.upperBound) else {\n                return 0\n            }\n            return (endTextLine.index - startTextLine.index) + 1\n        }\n\n        func moveLinesUp() {\n            guard let controller = textViewController else { return }\n            controller.moveLinesUp()\n        }\n\n        func moveLinesDown() {\n            guard let controller = textViewController else { return }\n            controller.moveLinesDown()\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/Models/EditorLayout/EditorLayout+StateRestoration.swift",
    "content": "//\n//  Editor+StateRestoration.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/3/23.\n//\n\nimport Foundation\nimport SwiftUI\nimport OrderedCollections\n\nextension EditorManager {\n    /// Restores the tab manager from a captured state obtained using `saveRestorationState`\n    /// - Parameter workspace: The workspace to retrieve state from.\n    func restoreFromState(_ workspace: WorkspaceDocument) {\n        defer {\n            // No matter what, set the workspace on each editor. Even if we fail to read data.\n            flattenedEditors.forEach { editor in\n                editor.workspace = workspace\n            }\n        }\n\n        do {\n            guard let data = workspace.getFromWorkspaceState(.openTabs) as? Data else {\n                return\n            }\n\n            let state = try JSONDecoder().decode(EditorRestorationState.self, from: data)\n\n            guard !state.groups.isEmpty else {\n                logger.warning(\"Empty Editor State found, restoring to clean editor state.\")\n                initCleanState()\n                return\n            }\n\n            guard let activeEditor = state.groups.find(\n                editor: state.activeEditor\n            ) ?? state.groups.findSomeEditor() else {\n                logger.warning(\"Editor state could not restore active editor.\")\n                initCleanState()\n                return\n            }\n\n            try fixRestoredEditorLayout(state.groups, workspace: workspace)\n\n            self.editorLayout = state.groups\n            self.activeEditor = activeEditor\n            switchToActiveEditor()\n        } catch {\n            logger.warning(\n                \"Could not restore editor state from saved data: \\(error.localizedDescription, privacy: .public)\"\n            )\n        }\n    }\n\n    /// Fix any hanging files after restoring from saved state.\n    ///\n    /// After decoding the state, we're left with `CEWorkspaceFile`s that don't exist in the file manager\n    /// so this function maps all those to 'real' files. Works recursively on all the tab groups.\n    /// - Parameters:\n    ///   - group: The tab group to fix.\n    ///   - fileManager: The file manager to use to map files.\n    private func fixRestoredEditorLayout(_ group: EditorLayout, workspace: WorkspaceDocument) throws {\n        switch group {\n        case let .one(data):\n            try fixEditor(data, workspace: workspace)\n        case let .vertical(splitData):\n            try splitData.editorLayouts.forEach { group in\n                try fixRestoredEditorLayout(group, workspace: workspace)\n            }\n        case let .horizontal(splitData):\n            try splitData.editorLayouts.forEach { group in\n                try fixRestoredEditorLayout(group, workspace: workspace)\n            }\n        }\n    }\n\n    private func findEditorLayout(group: EditorLayout, searchFor id: UUID) throws -> Editor? {\n        switch group {\n        case let .one(data):\n            return data.id == id ? data : nil\n        case let .vertical(splitData):\n            return try splitData.editorLayouts.compactMap { try findEditorLayout(group: $0, searchFor: id) }.first\n        case let .horizontal(splitData):\n            return try splitData.editorLayouts.compactMap { try findEditorLayout(group: $0, searchFor: id) }.first\n        }\n    }\n\n    /// Fixes any hanging files after restoring from saved state.\n    ///\n    /// Resolves all file references with the workspace's file manager to ensure any referenced files use their shared\n    /// object representation.\n    ///\n    /// - Parameters:\n    ///   - data: The tab group to fix.\n    ///   - fileManager: The file manager to use to map files.a\n    private func fixEditor(_ editor: Editor, workspace: WorkspaceDocument) throws {\n        guard let fileManager = workspace.workspaceFileManager else { return }\n        let resolvedTabs = editor\n            .tabs\n            .compactMap({ fileManager.getFile($0.file.url.path(percentEncoded: false), createIfNotFound: true) })\n            .map({ EditorInstance(workspace: workspace, file: $0) })\n\n        for tab in resolvedTabs {\n            try tab.file.loadCodeFile()\n        }\n\n        editor.workspace = workspace\n        editor.tabs = OrderedSet(resolvedTabs)\n\n        if let selectedTab = editor.selectedTab {\n            if let resolvedFile = fileManager.getFile(\n                selectedTab.file.url.path(percentEncoded: false),\n                createIfNotFound: true\n            ) {\n                editor.setSelectedTab(resolvedFile)\n            } else {\n                editor.setSelectedTab(nil)\n            }\n        }\n    }\n\n    func saveRestorationState(_ workspace: WorkspaceDocument) {\n        if let data = try? JSONEncoder().encode(\n            EditorRestorationState(activeEditor: activeEditor.id, groups: editorLayout)\n        ) {\n            workspace.addToWorkspaceState(key: .openTabs, value: data)\n        } else {\n            workspace.addToWorkspaceState(key: .openTabs, value: nil)\n        }\n    }\n}\n\nstruct EditorRestorationState: Codable {\n    var activeEditor: UUID\n    var groups: EditorLayout\n}\n\nextension EditorLayout: Codable {\n    fileprivate enum EditorLayoutType: String, Codable {\n        case one\n        case vertical\n        case horizontal\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case type\n        case tabs\n    }\n\n    init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        let type = try container.decode(EditorLayoutType.self, forKey: .type)\n        switch type {\n        case .one:\n            let editor = try container.decode(Editor.self, forKey: .tabs)\n            self = .one(editor)\n        case .vertical:\n            let editor = try container.decode(SplitViewData.self, forKey: .tabs)\n            self = .vertical(editor)\n        case .horizontal:\n            let editor = try container.decode(SplitViewData.self, forKey: .tabs)\n            self = .horizontal(editor)\n        }\n    }\n\n    func encode(to encoder: Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        switch self {\n        case let .one(data):\n            try container.encode(EditorLayoutType.one, forKey: .type)\n            try container.encode(data, forKey: .tabs)\n        case let .vertical(data):\n            try container.encode(EditorLayoutType.vertical, forKey: .type)\n            try container.encode(data, forKey: .tabs)\n        case let .horizontal(data):\n            try container.encode(EditorLayoutType.horizontal, forKey: .type)\n            try container.encode(data, forKey: .tabs)\n        }\n    }\n}\n\nextension SplitViewData: Codable {\n    fileprivate enum SplitViewAxis: String, Codable {\n        case vertical, horizontal\n\n        init(_ swiftUI: Axis) {\n            switch swiftUI {\n            case .vertical: self = .vertical\n            case .horizontal: self = .horizontal\n            }\n        }\n\n        var swiftUI: Axis {\n            switch self {\n            case .vertical: return .vertical\n            case .horizontal: return .horizontal\n            }\n        }\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case editorLayouts\n        case axis\n    }\n\n    convenience init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        let axis = try container.decode(SplitViewAxis.self, forKey: .axis).swiftUI\n        let editorLayouts = try container.decode([EditorLayout].self, forKey: .editorLayouts)\n        self.init(axis, editorLayouts: editorLayouts)\n    }\n\n    func encode(to encoder: Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encode(editorLayouts, forKey: .editorLayouts)\n        try container.encode(SplitViewAxis(axis), forKey: .axis)\n    }\n}\n\nextension Editor: Codable {\n    enum CodingKeys: String, CodingKey {\n        case tabs\n        case selectedTab\n        case id\n    }\n\n    convenience init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        let fileURLs = try container.decode([URL].self, forKey: .tabs)\n        let selectedTab = try? container.decode(URL.self, forKey: .selectedTab)\n        let id = try container.decode(UUID.self, forKey: .id)\n        self.init(\n            files: OrderedSet(fileURLs.map { CEWorkspaceFile(url: $0) }),\n            selectedTab: selectedTab == nil ? nil : EditorInstance(\n                workspace: nil,\n                file: CEWorkspaceFile(url: selectedTab!)\n            ),\n            parent: nil,\n            workspace: nil\n        )\n        self.id = id\n    }\n\n    func encode(to encoder: Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encode(tabs.map { $0.file.url }, forKey: .tabs)\n        try container.encode(selectedTab?.file.url, forKey: .selectedTab)\n        try container.encode(id, forKey: .id)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/Models/EditorLayout/EditorLayout.swift",
    "content": "//\n//  EditorLayout.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 06/02/2023.\n//\n\nimport Foundation\n\nenum EditorLayout: Equatable {\n    case one(Editor)\n    case vertical(SplitViewData)\n    case horizontal(SplitViewData)\n\n    /// Closes all tabs which present the given file\n    /// - Parameter file: a file.\n    func closeAllTabs(of file: CEWorkspaceFile) {\n        switch self {\n        case .one(let editor):\n            editor.removeTab(file)\n        case .vertical(let data), .horizontal(let data):\n            data.editorLayouts.forEach {\n                $0.closeAllTabs(of: file)\n            }\n        }\n    }\n\n    /// Returns some editor, except the given editor.\n    /// - Parameter except: the search will exclude this editor.\n    /// - Returns: Some editor.\n    func findSomeEditor(except: Editor? = nil) -> Editor? {\n        switch self {\n        case .one(let editor) where editor != except:\n            return editor\n        case .vertical(let data), .horizontal(let data):\n            for editorLayout in data.editorLayouts {\n                if let result = editorLayout.findSomeEditor(except: except), result != except {\n                    return result\n                }\n            }\n            return nil\n        default:\n            return nil\n        }\n    }\n\n    func find(editor id: UUID) -> Editor? {\n        switch self {\n        case .one(let editor):\n            if editor.id == id {\n                return editor\n            }\n        case .vertical(let splitViewData), .horizontal(let splitViewData):\n            for layout in splitViewData.editorLayouts {\n                if let editor = layout.find(editor: id) {\n                    return editor\n                }\n            }\n        }\n\n        return nil\n    }\n\n    /// Forms a set of all files currently represented by tabs.\n    func gatherOpenFiles() -> Set<CEWorkspaceFile> {\n        switch self {\n        case .one(let editor):\n            return Set(editor.tabs.map { $0.file })\n        case .vertical(let data), .horizontal(let data):\n            return data.editorLayouts.map { $0.gatherOpenFiles() }.reduce(into: []) { $0.formUnion($1) }\n        }\n    }\n\n    /// Flattens the splitviews.\n    mutating func flatten(parent: SplitViewData) {\n        switch self {\n        case .one:\n            break\n        case .horizontal(let data), .vertical(let data):\n            if data.editorLayouts.count == 1 {\n                let one = data.editorLayouts[0]\n                if case .one(let editor) = one {\n                    editor.parent = parent\n                }\n                self = one\n            } else {\n                data.flatten()\n            }\n        }\n    }\n\n    /// Gets flattened splitviews.\n    func getFlattened(parent: SplitViewData) -> [Editor] {\n        switch self {\n        case .one(let editor):\n            return [editor]\n        case .horizontal(let data), .vertical(let data):\n            if data.editorLayouts.count == 1 {\n                let one = data.editorLayouts[0]\n                if case .one(let editor) = one {\n                    return [editor]\n                }\n                return []\n            } else {\n                return data.getFlattened()\n            }\n        }\n    }\n\n    var isEmpty: Bool {\n        switch self {\n        case .one:\n            return false\n        case .vertical(let splitViewData), .horizontal(let splitViewData):\n            return splitViewData.editorLayouts.allSatisfy { editorLayout in\n                editorLayout.isEmpty\n            }\n        }\n    }\n\n    static func == (lhs: EditorLayout, rhs: EditorLayout) -> Bool {\n        switch (lhs, rhs) {\n        case let (.one(lhs), .one(rhs)):\n            return lhs == rhs\n        case let (.vertical(lhs), .vertical(rhs)):\n            return lhs.editorLayouts == rhs.editorLayouts\n        case let (.horizontal(lhs), .horizontal(rhs)):\n            return lhs.editorLayouts == rhs.editorLayouts\n        default:\n            return false\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/Models/EditorManager.swift",
    "content": "//\n//  TabManager.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 03/03/2023.\n//\n\nimport Combine\nimport Foundation\nimport DequeModule\nimport os\n\nclass EditorManager: ObservableObject {\n    let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? \"\", category: \"EditorManager\")\n\n    /// The complete editor layout.\n    @Published var editorLayout: EditorLayout\n\n    @Published var isFocusingActiveEditor: Bool\n\n    /// The Editor with active focus.\n    @Published var activeEditor: Editor {\n        didSet {\n            activeEditorHistory.prepend { [weak oldValue] in oldValue }\n            switchToActiveEditor()\n        }\n    }\n\n    /// History of last-used editors.\n    var activeEditorHistory: Deque<() -> Editor?> = []\n\n    /// notify listeners whenever tab selection changes on the active editor.\n    var tabBarTabIdSubject = PassthroughSubject<Editor.Tab?, Never>()\n    var cancellable: AnyCancellable?\n\n    // This caching mechanism is a temporary solution and is not optimized\n    @Published var updateCachedFlattenedEditors: Bool = true\n    var cachedFlettenedEditors: [Editor] = []\n    var flattenedEditors: [Editor] {\n        if updateCachedFlattenedEditors {\n            cachedFlettenedEditors = self.getFlattened()\n            updateCachedFlattenedEditors = false\n        }\n        return cachedFlettenedEditors\n    }\n\n    // MARK: - Init\n\n    init() {\n        let tab = Editor()\n        self.activeEditor = tab\n        self.activeEditorHistory.prepend { [weak tab] in tab }\n        self.editorLayout = .horizontal(.init(.horizontal, editorLayouts: [.one(tab)]))\n        self.isFocusingActiveEditor = false\n        switchToActiveEditor()\n    }\n\n    /// Initializes the editor manager's state to the \"initial\" state.\n    ///\n    /// Functionally identical to the initializer for this class.\n    func initCleanState() {\n        let tab = Editor()\n        self.activeEditor = tab\n        self.activeEditorHistory.prepend { [weak tab] in tab }\n        self.editorLayout = .horizontal(.init(.horizontal, editorLayouts: [.one(tab)]))\n        self.isFocusingActiveEditor = false\n        switchToActiveEditor()\n    }\n\n    /// Flattens the splitviews.\n    func flatten() {\n        switch editorLayout {\n        case .horizontal(let data), .vertical(let data):\n            data.flatten()\n        default:\n            break\n        }\n    }\n\n    /// Returns and array of flattened splitviews.\n    func getFlattened() -> [Editor] {\n        switch editorLayout {\n        case .horizontal(let data), .vertical(let data):\n            return data.getFlattened()\n        default:\n            return []\n        }\n    }\n\n    /// Opens a new tab in a editor.\n    /// - Parameters:\n    ///   - item: The tab to open.\n    ///   - editor: The editor to add the tab to. If nil, it is added to the active tab group.\n    ///   - asTemporary: Indicates whether the tab should be opened as a temporary tab or a permanent tab.\n    func openTab(item: CEWorkspaceFile, in editor: Editor? = nil, asTemporary: Bool = false) {\n        let editor = editor ?? activeEditor\n        editor.openTab(file: item, asTemporary: asTemporary)\n    }\n\n    /// bind active tap group to listen to file selection changes.\n    func switchToActiveEditor() {\n        cancellable?.cancel()\n        cancellable = nil\n        cancellable = activeEditor.$selectedTab\n            .sink { [weak self] tab in\n                self?.tabBarTabIdSubject.send(tab)\n            }\n    }\n\n    // MARK: - Close Editor\n\n    /// Close an editor and fix editor manager state, updating active editor, etc.\n    /// - Parameter editor: The editor to close\n    func closeEditor(_ editor: Editor) {\n        editor.close()\n        if activeEditor == editor {\n            setNewActiveEditor(excluding: editor)\n        }\n\n        flatten()\n        objectWillChange.send()\n        updateCachedFlattenedEditors = true\n    }\n\n    /// Set a new active editor.\n    /// - Parameter editor: The editor to exclude.\n    func setNewActiveEditor(excluding editor: Editor) {\n        activeEditorHistory.removeAll { $0() == nil || $0() == editor }\n        if activeEditorHistory.isEmpty {\n            activeEditor = findSomeEditor(excluding: editor)\n        } else {\n            activeEditor = activeEditorHistory.removeFirst()()!\n        }\n    }\n\n    /// Find some editor, or if one cannot be found set up the editor manager with a clean state.\n    /// - Parameter editor: The editor to exclude.\n    /// - Returns: Some editor, order is not guaranteed.\n    func findSomeEditor(excluding editor: Editor) -> Editor {\n        guard let someEditor = editorLayout.findSomeEditor(except: editor) else {\n            initCleanState()\n            return activeEditor\n        }\n        return someEditor\n    }\n\n    // MARK: - Focus\n\n    func toggleFocusingEditor(from editor: Editor) {\n        if !isFocusingActiveEditor {\n            activeEditor = editor\n        }\n        isFocusingActiveEditor.toggle()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/Models/Environment+ActiveEditor.swift",
    "content": "//\n//  Environment+ActiveEditor.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 06/03/2023.\n//\n\nimport SwiftUI\n\nstruct ActiveEditorEnvironmentKey: EnvironmentKey {\n    static var defaultValue = false\n}\n\nextension EnvironmentValues {\n    var isActiveEditor: Bool {\n        get { self[ActiveEditorEnvironmentKey.self] }\n        set { self[ActiveEditorEnvironmentKey.self] = newValue }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/Models/Restoration/EditorStateRestoration.swift",
    "content": "//\n//  EditorStateRestoration.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 6/20/25.\n//\n\nimport Foundation\nimport GRDB\nimport CodeEditSourceEditor\nimport OSLog\n\n/// CodeEdit attempts to store and retrieve editor state for open tabs to restore the user's scroll position and\n/// cursor positions between sessions. This class manages the storage mechanism to facilitate that feature.\n///\n/// This creates a sqlite database in the application support directory named `editor-restoration.db`.\n///\n/// To ensure we can query this quickly, this class is shared globally (to avoid having to use a database pool) and\n/// all writes and reads are synchronous.\n///\n/// # If changes are required\n///\n/// Use the database migrator in the initializer for this class, see GRDB's documentation for adding a migration\n/// version. **Do not ever** delete migration versions that have made it to a released version of CodeEdit.\nfinal class EditorStateRestoration {\n    /// Optional here so we can gracefully catch errors.\n    /// The nice thing is this feature is optional in that if we don't have it available the user's experience is\n    /// degraded but not catastrophic.\n    static let shared: EditorStateRestoration? = try? EditorStateRestoration()\n\n    private static let logger = Logger(\n        subsystem: Bundle.main.bundleIdentifier ?? \"\",\n        category: \"EditorStateRestoration\"\n    )\n\n    struct StateRestorationRecord: Codable, TableRecord, FetchableRecord, PersistableRecord {\n        let uri: String\n        let data: Data\n    }\n\n    struct StateRestorationData: Codable, Equatable {\n        // Cursor positions as range values (not row/column!)\n        let cursorPositions: [Range<Int>]\n        let scrollPositionX: Double\n        let scrollPositionY: Double\n\n        var scrollPosition: CGPoint {\n            CGPoint(x: scrollPositionX, y: scrollPositionY)\n        }\n\n        var editorCursorPositions: [CursorPosition] {\n            cursorPositions.map { CursorPosition(range: NSRange(start: $0.lowerBound, end: $0.upperBound)) }\n        }\n\n        init(cursorPositions: [CursorPosition], scrollPosition: CGPoint) {\n            self.cursorPositions = cursorPositions\n                .compactMap { $0.range }\n                .map { $0.location..<($0.location + $0.length) }\n            self.scrollPositionX = scrollPosition.x\n            self.scrollPositionY = scrollPosition.y\n        }\n    }\n\n    private var databaseQueue: DatabaseQueue?\n    private var databaseURL: URL\n\n    /// Create a new editor restoration object. Will connect to or create a SQLite db.\n    /// - Parameter databaseURL: The database URL to use. Must point to a file, not a directory. If left `nil`, will\n    ///                          create a new database named `editor-restoration.db` in the application support\n    ///                          directory.\n    init(_ databaseURL: URL? = nil) throws {\n        self.databaseURL = databaseURL ?? FileManager.default\n            .homeDirectoryForCurrentUser\n            .appending(path: \"Library/Application Support/CodeEdit\", directoryHint: .isDirectory)\n            .appending(path: \"editor-restoration.db\", directoryHint: .notDirectory)\n        try attemptMigration(retry: true)\n    }\n\n    func attemptMigration(retry: Bool) throws {\n        do {\n            let databaseQueue = try DatabaseQueue(path: self.databaseURL.absolutePath, configuration: .init())\n\n            var migrator = DatabaseMigrator()\n\n            migrator.registerMigration(\"Version 0\") {\n                try $0.create(table: \"stateRestorationRecord\") { table in\n                    table.column(\"uri\", .text).primaryKey().notNull()\n                    table.column(\"data\", .blob).notNull()\n                }\n            }\n\n            try migrator.migrate(databaseQueue)\n            self.databaseQueue = databaseQueue\n        } catch {\n            if retry {\n                // Try to delete the database on failure, might fix a corruption or version error.\n                try? FileManager.default.removeItem(at: databaseURL)\n                try attemptMigration(retry: false)\n\n                return // Ignore the original error if we're retrying\n            }\n            Self.logger.error(\"Failed to start database connection: \\(error)\")\n            throw error\n        }\n    }\n\n    /// Update saved restoration state of a document.\n    /// - Parameters:\n    ///   - documentUrl: The URL of the document.\n    ///   - data: The data to store for the file, retrieved using ``restorationState(for:)``.\n    func updateRestorationState(for documentUrl: URL, data: StateRestorationData) {\n        do {\n            let serializedData = try JSONEncoder().encode(data)\n            let dbRow = StateRestorationRecord(uri: documentUrl.absolutePath, data: serializedData)\n            try databaseQueue?.write { try dbRow.upsert($0) }\n        } catch {\n            Self.logger.error(\"Failed to save editor state: \\(error)\")\n        }\n    }\n\n    /// Find the restoration state for a document.\n    /// - Parameter documentUrl: The URL of the document.\n    /// - Returns: Any data saved for this file.\n    func restorationState(for documentUrl: URL) -> StateRestorationData? {\n        do {\n            guard let row = try databaseQueue?.read({\n                try StateRestorationRecord.fetchOne($0, key: documentUrl.absolutePath)\n            }) else {\n                return nil\n            }\n            let decodedData = try JSONDecoder().decode(StateRestorationData.self, from: row.data)\n            return decodedData\n        } catch {\n            Self.logger.error(\"Failed to find editor state for '\\(documentUrl.absolutePath)': \\(error)\")\n        }\n        return nil\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/Models/Restoration/UndoManagerRegistration.swift",
    "content": "//\n//  UndoManagerRegistration.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 6/27/25.\n//\n\nimport SwiftUI\nimport CodeEditTextView\n\n/// Very simple class for registering undo manager for files for a project session. This does not do any saving, it\n/// just stores the undo managers in memory and retrieves them as necessary for files.\n///\n/// Undo stacks aren't stored on `CEWorkspaceFile` or `CodeFileDocument` because:\n/// - `CEWorkspaceFile` can be refreshed and reloaded at any point.\n/// - `CodeFileDocument` is released once there are no editors viewing it.\n/// Undo stacks need to be retained for the duration of a workspace session, enduring editor closes..\nfinal class UndoManagerRegistration: ObservableObject {\n    private var managerMap: [String: CEUndoManager] = [:]\n\n    init() { }\n\n    /// Find or create a new undo manager.\n    /// - Parameter file: The file to create for.\n    /// - Returns: The undo manager for the given file.\n    func manager(forFile file: CEWorkspaceFile) -> CEUndoManager {\n        manager(forFile: file.url)\n    }\n\n    /// Find or create a new undo manager.\n    /// - Parameter path: The path of the file to create for.\n    /// - Returns: The undo manager for the given file.\n    func manager(forFile path: URL) -> CEUndoManager {\n        if let manager = managerMap[path.absolutePath] {\n            return manager\n        } else {\n            let newManager = CEUndoManager()\n            managerMap[path.absolutePath] = newManager\n            return newManager\n        }\n    }\n\n    /// Find or create a new undo manager.\n    /// - Parameter path: The path of the file to create for.\n    /// - Returns: The undo manager for the given file.\n    func managerIfExists(forFile path: URL) -> CEUndoManager? {\n        managerMap[path.absolutePath]\n    }\n}\n\nextension UndoManagerRegistration: CEWorkspaceFileManagerObserver {\n    /// Managers need to be cleared when the following is true:\n    /// - The file is not open in any editors\n    /// - The file is updated externally\n    ///\n    /// To handle this?\n    /// - When we receive a file update, if the file is not open in any editors we clear the undo stack\n    func fileManagerUpdated(updatedItems: Set<CEWorkspaceFile>) {\n        for file in updatedItems where file.fileDocument == nil {\n            managerMap.removeValue(forKey: file.url.absolutePath)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorFileTabCloseButton.swift",
    "content": "//\n//  FileEditorTabCloseButton.swift\n//  CodeEdit\n//\n//  Created by Albert Vinizhanau on 10/13/23.\n//\n\nimport Foundation\nimport SwiftUI\nimport Combine\n\nstruct EditorFileTabCloseButton: View {\n    var isActive: Bool\n    var isHoveringTab: Bool\n    var isDragging: Bool\n    var closeAction: () -> Void\n    @Binding var closeButtonGestureActive: Bool\n    var item: CEWorkspaceFile\n    @Binding var isHoveringClose: Bool\n\n    @State private var isDocumentEdited: Bool = false\n    @State private var id: Int = 0\n\n    var body: some View {\n        EditorTabCloseButton(\n            isActive: isActive,\n            isHoveringTab: isHoveringTab,\n            isDragging: isDragging,\n            closeAction: closeAction,\n            closeButtonGestureActive: $closeButtonGestureActive,\n            isDocumentEdited: isDocumentEdited,\n            isHoveringClose: $isHoveringClose\n        )\n        .id(id)\n        // Detects if file document changed, when this view created item.fileDocument is nil\n        .onReceive(item.fileDocumentPublisher, perform: { _ in\n            // Force re-render so isDocumentEdited publisher is updated\n            self.id += 1\n        })\n        .onReceive(\n            item.fileDocument?.isDocumentEditedPublisher.eraseToAnyPublisher() ?? Empty().eraseToAnyPublisher()\n        ) { newValue in\n            self.isDocumentEdited = newValue\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabBackground.swift",
    "content": "//\n//  EditorTabBackground.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 1/17/23.\n//\n\nimport SwiftUI\n\nstruct EditorTabBackground: View {\n    var isActive: Bool\n    var isPressing: Bool\n    var isDragging: Bool\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @Environment(\\.controlActiveState)\n    private var activeState\n\n    @Environment(\\.isActiveEditor)\n    private var isActiveEditor\n\n    private var inHoldingState: Bool {\n        isPressing || isDragging\n    }\n\n    var body: some View {\n        ZStack {\n            if isActive {\n                // Content background (visible if active)\n                if #available(macOS 26, *) {\n                    GlassEffectView()\n                } else {\n                    EffectView(.contentBackground)\n                }\n\n                // Accent color (visible if active)\n                Color(.controlAccentColor)\n                    .hueRotation(.degrees(-5))\n                    .opacity(\n                        colorScheme == .dark\n                        ? activeState == .inactive ? 0.22 : inHoldingState ? 0.33 : 0.26\n                        : activeState == .inactive ? 0.1 : inHoldingState ? 0.27 : 0.2\n                    )\n                    .saturation(isActiveEditor ? 1.0 : 0.0)\n            }\n\n            if colorScheme == .dark {\n                // Highlight (if in dark mode)\n                Color(.white)\n                    .blendMode(.plusLighter)\n                    .opacity(\n                        isActive\n                        ? activeState == .inactive ? 0.04 : inHoldingState ? 0.14 : 0.09\n                        : isPressing ? 0.05 : 0\n                    )\n            }\n\n            if isDragging && !isActive {\n                // Dragging color (if not active)\n                Color(.unemphasizedSelectedTextBackgroundColor)\n                    .opacity(0.85)\n            }\n\n            if !isActive && isPressing {\n                Color(.unemphasizedSelectedTextBackgroundColor)\n            }\n        }\n    }\n}\n\nstruct EditorTabBackground_Previews: PreviewProvider {\n    static var previews: some View {\n        EditorTabBackground(isActive: false, isPressing: false, isDragging: false)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabButtonStyle.swift",
    "content": "//\n//  EditorTabButtonStyle.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 6/4/22.\n//\n\nimport SwiftUI\n\nstruct EditorTabButtonStyle: ButtonStyle {\n    @Environment(\\.colorScheme)\n    var colorScheme\n\n    @Binding private var isPressing: Bool\n\n    init(isPressing: Binding<Bool>) {\n        self._isPressing = isPressing\n    }\n\n    func makeBody(configuration: Configuration) -> some View {\n        configuration.label\n            .onChange(of: configuration.isPressed) { _, isPressed in\n                self.isPressing = isPressed\n            }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift",
    "content": "//\n//  EditorTabCloseButton.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 1/17/23.\n//\n\nimport SwiftUI\n\nstruct EditorTabCloseButton: View {\n    var isActive: Bool\n    var isHoveringTab: Bool\n    var isDragging: Bool\n    var closeAction: () -> Void\n    @Binding var closeButtonGestureActive: Bool\n    var isDocumentEdited: Bool = false\n\n    @Environment(\\.colorScheme)\n    var colorScheme\n\n    @State private var isPressingClose: Bool = false\n    @Binding var isHoveringClose: Bool\n\n    let buttonSize: CGFloat = 16\n\n    var body: some View {\n        HStack(alignment: .center) {\n            Image(systemName: isDocumentEdited && !isHoveringTab ? \"circlebadge.fill\" : \"xmark\")\n                .font(\n                    .system(\n                        size: isDocumentEdited && !isHoveringTab ? 9.5 : 11.5,\n                        weight: .regular,\n                        design: .rounded\n                    )\n                )\n                .foregroundColor(\n                    isActive\n                    ? colorScheme == .dark ? .primary : Color(.controlAccentColor)\n                    : .secondary\n                )\n        }\n        .frame(width: buttonSize, height: buttonSize)\n        .background(backgroundColor)\n        .foregroundColor(isPressingClose ? .primary : .secondary)\n        .if(.tahoe) {\n            $0.clipShape(Circle())\n        } else: {\n            $0.clipShape(RoundedRectangle(cornerRadius: 2))\n        }\n        .contentShape(Rectangle())\n        .gesture(\n            DragGesture(minimumDistance: 0)\n                .onChanged({ _ in\n                    isPressingClose = true\n                    closeButtonGestureActive = true\n                })\n                .onEnded({ value in\n                    // If the final position of the mouse is within the bounds of the\n                    // close button then close the tab\n                    if value.location.x > 0\n                        && value.location.x < buttonSize\n                        && value.location.y > 0\n                        && value.location.y < buttonSize {\n                        closeAction()\n                    }\n                    isPressingClose = false\n                    closeButtonGestureActive = false\n                })\n        )\n        .onHover { hover in\n            isHoveringClose = hover\n        }\n        .accessibilityAddTraits(.isButton)\n        .accessibilityLabel(Text(\"Close\"))\n        // Only show when the mouse is hovering and there is no tab dragging.\n        .opacity((isHoveringTab || isDocumentEdited == true) && !isDragging ? 1 : 0)\n        .animation(.easeInOut(duration: 0.08), value: isHoveringTab)\n        .padding(.leading, 4)\n    }\n\n    @ViewBuilder var backgroundColor: some View {\n        if colorScheme == .dark {\n            let opacity: Double = if isPressingClose {\n                0.10\n            } else if isHoveringClose {\n                0.05\n            } else {\n                0\n            }\n\n            Color(nsColor: .white)\n                .opacity(opacity)\n        } else {\n            let opacity: Double = if isPressingClose {\n                0.25\n            } else if isHoveringClose {\n                if isActive {\n                    0.10\n                } else {\n                    0.06\n                }\n            } else {\n                0.0\n            }\n\n            Color(nsColor: isActive ? .controlAccentColor : .systemGray)\n                .opacity(opacity)\n        }\n    }\n}\n\n@available(macOS 14.0, *)\n#Preview {\n    @Previewable @State var closeButtonGestureActive: Bool = false\n    @Previewable @State var isHoveringClose: Bool = false\n\n    return EditorTabCloseButton(\n        isActive: false,\n        isHoveringTab: false,\n        isDragging: false,\n        closeAction: { print(\"Close tab\") },\n        closeButtonGestureActive: $closeButtonGestureActive,\n        isHoveringClose: $isHoveringClose\n    )\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift",
    "content": "//\n//  EditorTabView.swift\n//  CodeEdit\n//\n//  Created by Lukas Pistrol on 17.03.22.\n//\n\nimport SwiftUI\n\nstruct EditorTabView: View {\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @Environment(\\.controlActiveState)\n    private var activeState\n\n    @Environment(\\.isActiveEditor)\n    private var isActiveEditor\n\n    @Environment(\\.isFullscreen)\n    private var isFullscreen\n\n    @EnvironmentObject var workspace: WorkspaceDocument\n    @EnvironmentObject private var editorManager: EditorManager\n\n    @StateObject private var fileObserver: EditorTabFileObserver\n\n    @AppSettings(\\.general.fileIconStyle)\n    var fileIconStyle\n\n    /// Is cursor hovering over the entire tab.\n    @State private var isHovering: Bool = false\n\n    /// Is cursor hovering over the close button.\n    @State private var isHoveringClose: Bool = false\n\n    /// Is entire tab being pressed.\n    @State private var isPressing: Bool = false\n\n    /// Is close button being pressed.\n    @State private var isPressingClose: Bool = false\n\n    /// A bool state for going-in animation.\n    ///\n    /// By default, this value is `false`. When the root view is appeared, it turns `true`.\n    @State private var isAppeared: Bool = false\n\n    @State private var keyMonitor: Any?\n\n    /// The id associating with the tab that is currently being dragged.\n    ///\n    /// When `nil`, then there is no tab being dragged.\n    private var draggingTabId: CEWorkspaceFile.ID?\n\n    private var onDragTabId: CEWorkspaceFile.ID?\n\n    @Binding private var closeButtonGestureActive: Bool\n\n    @EnvironmentObject private var editor: Editor\n\n    /// The file item associated with the current tab.\n    ///\n    /// You can get tab-related information from here, like `label`, `icon`, etc.\n    private let tabFile: CEWorkspaceFile\n\n    var index: Int\n\n    private var isTemporary: Bool {\n        editor.temporaryTab?.file == tabFile\n    }\n\n    /// Is the current tab the active tab.\n    private var isActive: Bool {\n        tabFile == editor.selectedTab?.file\n    }\n\n    /// Is the current tab being dragged.\n    private var isDragging: Bool {\n        draggingTabId == tabFile.id\n    }\n\n    /// Is the current tab being held (by click and hold, not drag).\n    ///\n    /// I use the name `inHoldingState` to avoid any confusion with `isPressing` and `isDragging`.\n    private var inHoldingState: Bool {\n        isPressing || isDragging\n    }\n\n    /// Switch the active tab to current tab.\n    private func switchAction() {\n        // Only set the `selectedId` when they are not equal to avoid performance issue for now.\n        editorManager.activeEditor = editor\n        if editor.selectedTab?.file != tabFile {\n            let tabItem = EditorInstance(workspace: workspace, file: tabFile)\n            editor.setSelectedTab(tabFile)\n            editor.clearFuture()\n            editor.addToHistory(tabItem)\n        }\n    }\n\n    /// Close the current tab.\n    func closeAction() {\n        isAppeared = false\n        editor.closeTab(file: tabFile)\n    }\n\n    init(\n        file: CEWorkspaceFile,\n        index: Int,\n        draggingTabId: CEWorkspaceFile.ID?,\n        onDragTabId: CEWorkspaceFile.ID?,\n        closeButtonGestureActive: Binding<Bool>\n    ) {\n        self.tabFile = file\n        self.index = index\n        self.draggingTabId = draggingTabId\n        self.onDragTabId = onDragTabId\n        self._closeButtonGestureActive = closeButtonGestureActive\n        self._fileObserver = StateObject(wrappedValue: EditorTabFileObserver(file: file))\n    }\n\n    @ViewBuilder var content: some View {\n        HStack(spacing: 0.0) {\n\n            if #unavailable(macOS 26) {\n                EditorTabDivider()\n                    .opacity((isActive || inHoldingState) ? 0.0 : 1.0)\n            }\n            // Tab content (icon and text).\n            HStack(alignment: .center, spacing: 3) {\n                Image(nsImage: tabFile.nsIcon)\n                    .frame(width: 16, height: 16)\n                    .foregroundColor(\n                        fileIconStyle == .color\n                        && activeState != .inactive && isActiveEditor\n                        ? tabFile.iconColor\n                        : .secondary\n                    )\n                Text(tabFile.name)\n                    .font(\n                        isTemporary\n                        ? .system(size: 11.0).italic()\n                        : .system(size: 11.0)\n                    )\n                    .lineLimit(1)\n                    .strikethrough(fileObserver.isDeleted, color: .primary)\n            }\n            .frame(maxHeight: .infinity) // To max-out the parent (tab bar) area.\n            .accessibilityElement(children: .ignore)\n            .accessibilityAddTraits(.isStaticText)\n            .accessibilityLabel(tabFile.name)\n            .padding(.horizontal, 20)\n            .overlay {\n                ZStack {\n                    // Close Button with is file changed indicator\n                    EditorFileTabCloseButton(\n                        isActive: isActive,\n                        isHoveringTab: isHovering,\n                        isDragging: draggingTabId != nil || onDragTabId != nil,\n                        closeAction: closeAction,\n                        closeButtonGestureActive: $closeButtonGestureActive,\n                        item: tabFile,\n                        isHoveringClose: $isHoveringClose\n                    )\n                }\n                .frame(maxWidth: .infinity, alignment: .leading)\n            }\n            .if(.tahoe) {\n                $0.padding(.horizontal, 1.5)\n            }\n            .opacity(\n                // Inactive states for tab bar item content.\n                activeState != .inactive\n                ? 1.0\n                : isActive ? 0.6 : 0.4\n            )\n            if #unavailable(macOS 26) {\n                EditorTabDivider()\n                    .opacity((isActive || inHoldingState) ? 0.0 : 1.0)\n            }\n        }\n        .foregroundColor(\n            isActive && isActiveEditor\n            ? (\n                colorScheme != .dark\n                ? Color(nsColor: .controlAccentColor)\n                : .primary\n            )\n            : .primary\n        )\n        .frame(maxHeight: .infinity) // To vertically max-out the parent (tab bar) area.\n        .contentShape(Rectangle()) // Make entire area clickable.\n        .onHover { hover in\n            isHovering = hover\n            DispatchQueue.main.async {\n                if hover {\n                    NSCursor.arrow.push()\n                } else {\n                    NSCursor.pop()\n                }\n            }\n        }\n        .onAppear {\n            keyMonitor = NSEvent.addLocalMonitorForEvents(matching: .otherMouseDown) { event in\n                if self.isHovering && event.type == .otherMouseDown && event.buttonNumber == 2 {\n                    DispatchQueue.main.async {\n                        editor.closeTab(file: tabFile)\n                    }\n                }\n                return event\n            }\n        }\n        .onDisappear {\n            if let keyMonitor = keyMonitor {\n                NSEvent.removeMonitor(keyMonitor)\n                self.keyMonitor = nil\n            }\n        }\n    }\n\n    var body: some View {\n        // We don't use a button here so that accessibility isn't broken.\n        content\n            .background {\n                EditorTabBackground(isActive: isActive, isPressing: isPressing, isDragging: isDragging)\n                    .animation(.easeInOut(duration: 0.08), value: isHovering)\n            }\n            .if(.tahoe) {\n                if #available(macOS 26, *) {\n                    $0.clipShape(Capsule()).clipped().containerShape(Capsule())\n                }\n            }\n            // TODO: Enable the following code snippet when dragging-out behavior should be allowed.\n            // Since we didn't handle the drop-outside event, dragging-out is disabled for now.\n            //            .onDrag({\n            //                onDragTabId = item.tabID\n            //                return .init(object: NSString(string: \"\\(item.tabID)\"))\n            //            })\n            //        }\n            .simultaneousGesture(\n                DragGesture(minimumDistance: 0) // simultaneousGesture means this won't move the view.\n                    .onChanged({ _ in\n                        if !isHoveringClose {\n                            isPressing = true\n                        }\n                    })\n                    .onEnded({ _ in\n                        if isPressing {\n                            switchAction()\n                        }\n                        isPressing = false\n                    })\n            )\n            .simultaneousGesture(\n                TapGesture(count: 2)\n                    .onEnded { _ in\n                        if isTemporary {\n                            editor.temporaryTab = nil\n                        }\n                    }\n            )\n            .zIndex(isActive ? 2 : (isDragging ? 3 : (isPressing ? 1 : 0)))\n            .id(tabFile.id)\n            .tabBarContextMenu(item: tabFile, isTemporary: isTemporary)\n            .accessibilityElement(children: .contain)\n            .onAppear {\n                workspace.workspaceFileManager?.addObserver(fileObserver)\n            }\n            .onDisappear {\n                workspace.workspaceFileManager?.removeObserver(fileObserver)\n            }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/TabBar/Tabs/Tab/Models/EditorItemID.swift",
    "content": "//\n//  EditorTabID.swift\n//  \n//\n//  Created by Pavel Kasila on 30.04.22.\n//\n\nimport Foundation\n\n/// Enum to represent item's ID to tab bar\nenum EditorTabID: Codable, Identifiable, Hashable {\n    var id: String {\n        switch self {\n        case .codeEditor(let path):\n            return \"codeEditor_\\(path)\"\n        case .extensionInstallation(let id):\n            return \"extensionInstallation_\\(id.uuidString)\"\n        }\n    }\n\n    /// Represents code editor tab\n    case codeEditor(String)\n\n    /// Represents extension installation tab\n    case extensionInstallation(UUID)\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/TabBar/Tabs/Tab/Models/EditorTabFileObserver.swift",
    "content": "//\n//  EditorTabFileObserver.swift\n//  CodeEdit\n//\n//  Created by Filipp Kuznetsov on 25.02.2025.\n//\n\nimport Foundation\nimport SwiftUI\n\n/// Observer ViewModel for tracking file deletion\n@MainActor\nfinal class EditorTabFileObserver: ObservableObject,\n    CEWorkspaceFileManagerObserver {\n    @Published private(set) var isDeleted = false\n\n    private let tabFile: CEWorkspaceFile\n\n    init(file: CEWorkspaceFile) {\n        self.tabFile = file\n    }\n\n    nonisolated func fileManagerUpdated(updatedItems: Set<CEWorkspaceFile>) {\n        Task { @MainActor in\n            if let parent = tabFile.parent, updatedItems.contains(parent) {\n                isDeleted = tabFile.doesExist == false\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/TabBar/Tabs/Tab/Models/EditorTabRepresentable.swift",
    "content": "//\n//  EditorTabRepresentable.swift\n//  \n//\n//  Created by Pavel Kasila on 30.04.22.\n//\n\nimport SwiftUI\n\n/// Protocol for data passed to EditorTabView to conform to\nprotocol EditorTabRepresentable {\n    /// Unique tab identifier\n    var tabID: EditorTabID { get }\n    /// String to be shown as tab's title\n    var name: String { get }\n    /// Image to be shown as tab's icon\n    var icon: Image { get }\n    /// Color of the tab's icon\n    var iconColor: Color { get }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/TabBar/Tabs/Views/EditorTabs.swift",
    "content": "//\n//  EditorTabs.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 9/7/23.\n//\n\nimport SwiftUI\n\n// Disable the rule because the tab bar view is fairly complicated.\n// It has the gesture implementation and its animations.\n// I am now also disabling `file_length` rule because the dragging algorithm (with UX) is complex.\n// swiftlint:disable file_length type_body_length\n// - TODO: EditorTabView drop-outside event handler.\n\nstruct EditorTabs: View {\n    typealias TabID = CEWorkspaceFile.ID\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    /// The workspace document.\n    @EnvironmentObject private var workspace: WorkspaceDocument\n\n    @EnvironmentObject private var editor: Editor\n\n    /// The tab id of current dragging tab.\n    ///\n    /// It will be `nil` when there is no tab dragged currently.\n    @State private var draggingTabId: TabID?\n\n    @State private var onDragTabId: TabID?\n\n    /// The start location of dragging.\n    ///\n    /// When there is no tab being dragged, it will be `nil`.\n    /// - TODO: Check if I can use `value.startLocation` trustfully.\n    @State private var draggingStartLocation: CGFloat?\n\n    /// The last location of dragging.\n    ///\n    /// This is used to determine the dragging direction.\n    /// - TODO: Check if I can use `value.translation` instead.\n    @State private var draggingLastLocation: CGFloat?\n\n    /// Current opened tabs.\n    ///\n    /// This is a copy of `workspace.selectionState.openedTabs`.\n    /// I am making a copy of it because using state will hugely improve the dragging performance.\n    /// Updating ObservedObject too often will generate lags.\n    @State private var openedTabs: [TabID] = []\n\n    /// A map of tab width.\n    ///\n    /// All width are measured dynamically (so it can also fit the Xcode tab bar style).\n    /// This is used to be added on the offset of current dragging tab in order to make a smooth\n    /// dragging experience.\n    @State private var tabWidth: [TabID: CGFloat] = [:]\n\n    /// A map of tab location (CGRect).\n    ///\n    /// All locations are measured dynamically.\n    /// This is used to compute when we should swap two tabs based on current cursor location.\n    @State private var tabLocations: [TabID: CGRect] = [:]\n\n    /// A map of tab offsets.\n    ///\n    /// This is used to determine the tab offset of every tab (by their tab id) while dragging.\n    @State private var tabOffsets: [TabID: CGFloat] = [:]\n\n    /// This state is used to detect if the mouse is hovering over tabs.\n    /// If it is true, then we do not update the expected tab width immediately.\n    @State private var isHoveringOverTabs: Bool = false\n\n    /// This state is used to detect if the dragging type should be changed from DragGesture to OnDrag.\n    /// It is basically switched when vertical displacement is exceeding the threshold.\n    @State private var shouldOnDrag: Bool = false\n\n    /// Is current `onDrag` over tabs?\n    ///\n    /// When it is true, then the `onDrag` is over the tabs, then we leave the space for dragged tab.\n    /// When it is false, then the dragging cursor is outside the tab bar, then we should shrink the space.\n    ///\n    /// - TODO: The change of this state is overall incorrect. Should move it into workspace state.\n    @State private var isOnDragOverTabs: Bool = false\n\n    /// The last location of `onDrag`.\n    ///\n    /// It can be used on reordering algorithm of `onDrag` (detecting when should we switch two tabs).\n    @State private var onDragLastLocation: CGPoint?\n\n    @State private var closeButtonGestureActive: Bool = false\n\n    @State private var scrollOffset: CGFloat = 0\n\n    @State private var scrollTrailingOffset: CGFloat? = 0\n\n    // Disable the rule because this function is implementing the drag gesture and its animations.\n    // It is fairly complicated, so ignore the function body length limitation for now.\n    // swiftlint:disable function_body_length cyclomatic_complexity\n    private func makeTabDragGesture(id: TabID) -> some Gesture {\n        return DragGesture(minimumDistance: 2, coordinateSpace: .global)\n            .onChanged({ value in\n                if closeButtonGestureActive {\n                    return\n                }\n\n                if draggingTabId != id {\n                    shouldOnDrag = false\n                    draggingTabId = id\n                    draggingStartLocation = value.startLocation.x\n                    draggingLastLocation = value.location.x\n                }\n                // TODO: Enable this code snippet when re-enabling dragging-out behavior.\n                // I disabled (1 == 0) this behavior for now as dragging-out behavior isn't allowed.\n                if 1 == 0 && abs(value.location.y - value.startLocation.y) > EditorTabBarView.height {\n                    shouldOnDrag = true\n                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {\n                        shouldOnDrag = false\n                        draggingStartLocation = nil\n                        draggingLastLocation = nil\n                        draggingTabId = nil\n                        withAnimation(.easeInOut(duration: 0.25)) {\n                            // Clean the tab offsets.\n                            tabOffsets = [:]\n                        }\n                    })\n                    return\n                }\n                // Get the current cursor location.\n                let currentLocation = value.location.x\n                guard let startLocation = draggingStartLocation,\n                      let currentIndex = openedTabs.firstIndex(of: id),\n                      let currentTabWidth = tabWidth[id],\n                      let lastLocation = draggingLastLocation\n                else { return }\n                let dragDifference = currentLocation - lastLocation\n                let previousIndex = currentIndex > 0 ? currentIndex - 1 : nil\n                let nextIndex = currentIndex < openedTabs.count - 1 ? currentIndex + 1 : nil\n                tabOffsets[id] = currentLocation - startLocation\n                // Interacting with the previous tab.\n                if previousIndex != nil && dragDifference < 0 {\n                    // Wrap `previousTabIndex` because it may be `nil`.\n                    guard let previousTabIndex = previousIndex,\n                          let previousTabLocation = tabLocations[openedTabs[previousTabIndex]],\n                          let previousTabWidth = tabWidth[openedTabs[previousTabIndex]]\n                    else { return }\n                    if currentLocation < max(\n                        previousTabLocation.maxX - previousTabWidth * 0.1,\n                        previousTabLocation.minX + currentTabWidth * 0.9\n                    ) {\n                        let changing = previousTabWidth - 1 // One offset for overlapping divider.\n                        draggingStartLocation! -= changing\n                        withAnimation {\n                            tabOffsets[id]! += changing\n                            openedTabs.move(\n                                fromOffsets: IndexSet(integer: previousTabIndex),\n                                toOffset: currentIndex + 1\n                            )\n                        }\n                        return\n                    }\n                }\n                // Interacting with the next tab.\n                if nextIndex != nil && dragDifference > 0 {\n                    // Wrap `previousTabIndex` because it may be `nil`.\n                    guard let nextTabIndex = nextIndex,\n                          let nextTabLocation = tabLocations[openedTabs[nextTabIndex]],\n                          let nextTabWidth = tabWidth[openedTabs[nextTabIndex]]\n                    else { return }\n                    if currentLocation > min(\n                        nextTabLocation.minX + nextTabWidth * 0.1,\n                        nextTabLocation.maxX - currentTabWidth * 0.9\n                    ) {\n                        let changing = nextTabWidth - 1 // One offset for overlapping divider.\n                        draggingStartLocation! += changing\n                        withAnimation {\n                            tabOffsets[id]! -= changing\n                            openedTabs.move(\n                                fromOffsets: IndexSet(integer: nextTabIndex),\n                                toOffset: currentIndex\n                            )\n                        }\n                        return\n                    }\n                }\n                // Only update the last dragging location when there is enough offset.\n                if draggingLastLocation == nil || abs(value.location.x - draggingLastLocation!) >= 10 {\n                    draggingLastLocation = value.location.x\n                }\n            })\n            .onEnded({ _ in\n                shouldOnDrag = false\n                draggingStartLocation = nil\n                draggingLastLocation = nil\n                withAnimation(.easeInOut(duration: 0.25)) {\n                    tabOffsets = [:]\n                }\n                DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {\n                    draggingTabId = nil\n                }\n                // Sync the workspace's `openedTabs` 150ms after animation is finished.\n                // In order to avoid the lag due to the update of workspace state.\n                DispatchQueue.main.asyncAfter(deadline: .now() + 0.40) {\n                    if draggingStartLocation == nil {\n                        editor.tabs = .init(openedTabs.compactMap { id in\n                            editor.tabs.first { $0.file.id == id }\n                        })\n                        // workspace.reorderedTabs(openedTabs: openedTabs)\n                        // TODO: Fix save state\n                    }\n                }\n            })\n    }\n\n    private func makeTabItemGeometryReader(id: TabID) -> some View {\n        GeometryReader { tabItemGeoReader in\n            Rectangle()\n                .foregroundColor(.clear)\n                .onAppear {\n                    tabWidth[id] = tabItemGeoReader.size.width\n                    tabLocations[id] = tabItemGeoReader\n                        .frame(in: .global)\n                }\n                .onChange(\n                    of: tabItemGeoReader.frame(in: .global)\n                ) { _, tabCGRect in\n                    tabLocations[id] = tabCGRect\n                }\n                .onChange(\n                    of: tabItemGeoReader.size.width\n                ) { _, newWidth in\n                    tabWidth[id] = newWidth\n                }\n        }\n    }\n\n    /// Called when the tab count changes or the temporary tab changes.\n    /// - Parameter geometryProxy: The geometry proxy to calculate the new width using.\n    private func updateForTabCountChange(geometryProxy: GeometryProxy) {\n        openedTabs = editor.tabs.map(\\.file.id)\n    }\n\n    // swiftlint:enable function_body_length cyclomatic_complexity\n\n    var body: some View {\n        GeometryReader { geometryProxy in\n            TrackableScrollView(\n                .horizontal,\n                showIndicators: false,\n                contentOffset: $scrollOffset,\n                contentTrailingOffset: $scrollTrailingOffset\n            ) {\n                ScrollViewReader { scrollReader in\n                    HStack(\n                        alignment: .center,\n                        spacing: -1 // Negative spacing for overlapping the divider.\n                    ) {\n                        ForEach(Array(openedTabs.enumerated()), id: \\.element) { index, id in\n                            if let item = editor.tabs.first(where: { $0.file.id == id }) {\n                                if index != 0\n                                    && editor.selectedTab?.file.id != id\n                                    && editor.selectedTab?.file.id != openedTabs[index - 1] {\n                                    EditorTabDivider()\n                                }\n\n                                EditorTabView(\n                                    file: item.file,\n                                    index: index,\n                                    draggingTabId: draggingTabId,\n                                    onDragTabId: onDragTabId,\n                                    closeButtonGestureActive: $closeButtonGestureActive\n                                )\n                                .transition(\n                                    .asymmetric(\n                                        insertion: .offset(x: -14).combined(with: .opacity),\n                                        removal: .opacity\n                                    )\n                                )\n                                .frame(height: EditorTabBarView.height)\n                                .background(makeTabItemGeometryReader(id: id))\n                                .offset(x: tabOffsets[id] ?? 0, y: 0)\n                                .simultaneousGesture(\n                                    makeTabDragGesture(id: id),\n                                    including: shouldOnDrag ? .subviews : .all\n                                )\n                                // TODO: Detect the onDrag outside of tab bar.\n                                // Detect the drop action of each tab.\n                                .onDrop(\n                                    of: [.utf8PlainText], // TODO: Make a unique type for it.\n                                    delegate: EditorTabOnDropDelegate(\n                                        currentTabId: id,\n                                        openedTabs: $openedTabs,\n                                        onDragTabId: $onDragTabId,\n                                        onDragLastLocation: $onDragLastLocation,\n                                        isOnDragOverTabs: $isOnDragOverTabs,\n                                        tabWidth: $tabWidth\n                                    )\n                                )\n\n                                if index < openedTabs.count - 1\n                                    && editor.selectedTab?.file.id != id\n                                    && editor.selectedTab?.file.id != openedTabs[index + 1] {\n                                    EditorTabDivider()\n                                }\n                            }\n                        }\n                    }\n                    .onAppear {\n                        openedTabs = editor.tabs.map(\\.file.id)\n                        // On first tab appeared, jump to the corresponding position.\n                        scrollReader.scrollTo(editor.selectedTab)\n                    }\n                    .onChange(of: editor.tabs) { tabs, newValue in\n                        if tabs.count == newValue.count {\n                            updateForTabCountChange(geometryProxy: geometryProxy)\n                        } else {\n                            withAnimation(\n                                .easeOut(duration: 0.20)\n                            ) {\n                                updateForTabCountChange(geometryProxy: geometryProxy)\n                            }\n                        }\n                        Task {\n                            try? await Task.sleep(for: .milliseconds(300))\n                            withAnimation {\n                                scrollReader.scrollTo(editor.selectedTab?.file.id)\n                            }\n                        }\n                    }\n                    // When selected tab is changed, scroll to it if possible.\n                    .onChange(of: editor.selectedTab) { _, newValue in\n                        withAnimation {\n                            scrollReader.scrollTo(newValue?.file.id)\n                        }\n                    }\n\n                    // When window size changes, re-compute the expected tab width.\n                    .onChange(of: geometryProxy.size.width) { _, _ in\n                        withAnimation {\n                            scrollReader.scrollTo(editor.selectedTab?.file.id)\n                        }\n                    }\n                    // When user is not hovering anymore, re-compute the expected tab width immediately.\n                    .onHover { isHovering in\n                        isHoveringOverTabs = isHovering\n                    }\n                    .frame(height: EditorTabBarView.height)\n                }\n\n                // To fill up the parent space of tab bar.\n                .frame(maxWidth: .infinity)\n            }\n            .overlay(alignment: .leading) {\n                EditorTabsOverflowShadow(\n                    width: colorScheme == .dark ? 5 : 7,\n                    startPoint: .leading,\n                    endPoint: .trailing\n                )\n                .opacity(scrollOffset >= 0 ? 0 : 1)\n            }\n            .overlay(alignment: .trailing) {\n                EditorTabsOverflowShadow(\n                    width: colorScheme == .dark ? 5 : 7,\n                    startPoint: .trailing,\n                    endPoint: .leading\n                )\n                .opacity((scrollTrailingOffset ?? 0) <= 0 ? 0 : 1)\n            }\n            .if(.tahoe) {\n                if #available(macOS 26.0, *) {\n                    // Unfortunate triple if here due to needing to compile on\n                    // earlier Xcodes.\n#if compiler(>=6.2)\n                    $0.background(GlassEffectView(tintColor: .tertiarySystemFill))\n                        .clipShape(Capsule())\n                        .clipped()\n#else\n                    $0\n#endif\n                }\n            }\n        }\n    }\n\n    private struct EditorTabOnDropDelegate: DropDelegate {\n        private let currentTabId: TabID\n        @Binding private var openedTabs: [TabID]\n        @Binding private var onDragTabId: TabID?\n        @Binding private var onDragLastLocation: CGPoint?\n        @Binding private var isOnDragOverTabs: Bool\n        @Binding private var tabWidth: [TabID: CGFloat]\n\n        public init(\n            currentTabId: TabID,\n            openedTabs: Binding<[TabID]>,\n            onDragTabId: Binding<TabID?>,\n            onDragLastLocation: Binding<CGPoint?>,\n            isOnDragOverTabs: Binding<Bool>,\n            tabWidth: Binding<[TabID: CGFloat]>\n        ) {\n            self.currentTabId = currentTabId\n            self._openedTabs = openedTabs\n            self._onDragTabId = onDragTabId\n            self._onDragLastLocation = onDragLastLocation\n            self._isOnDragOverTabs = isOnDragOverTabs\n            self._tabWidth = tabWidth\n        }\n\n        func dropEntered(info: DropInfo) {\n            isOnDragOverTabs = true\n            guard let onDragTabId,\n                  currentTabId != onDragTabId,\n                  let from = openedTabs.firstIndex(of: onDragTabId),\n                  let toIndex = openedTabs.firstIndex(of: currentTabId)\n            else { return }\n            if openedTabs[toIndex] != onDragTabId {\n                withAnimation {\n                    openedTabs.move(\n                        fromOffsets: IndexSet(integer: from),\n                        toOffset: toIndex > from ? toIndex + 1 : toIndex\n                    )\n                }\n            }\n        }\n\n        func dropExited(info: DropInfo) {\n            // Do nothing.\n        }\n\n        func dropUpdated(info: DropInfo) -> DropProposal? {\n            return DropProposal(operation: .move)\n        }\n\n        func performDrop(info: DropInfo) -> Bool {\n            isOnDragOverTabs = false\n            onDragTabId = nil\n            onDragLastLocation = nil\n            return true\n        }\n    }\n}\n\n// swiftlint:enable file_length type_body_length\n"
  },
  {
    "path": "CodeEdit/Features/Editor/TabBar/Tabs/Views/EditorTabsOverflowShadow.swift",
    "content": "//\n//  EditorTabsOverflowShadow.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 8/22/23.\n//\n\nimport SwiftUI\n\nstruct EditorTabsOverflowShadow: View {\n    var width: CGFloat\n    var startPoint: UnitPoint\n    var endPoint: UnitPoint\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @Environment(\\.controlActiveState)\n    private var activeState\n\n    var body: some View {\n        Rectangle()\n            .frame(maxHeight: .infinity)\n            .frame(width: width)\n            .foregroundColor(.clear)\n            .background(\n                LinearGradient(\n                    gradient: Gradient(\n                        stops: [\n                            Gradient.Stop(color: .black.opacity(0.75), location: 0),\n                            Gradient.Stop(color: .black.opacity(0.25), location: 0.5),\n                            Gradient.Stop(color: .black.opacity(0), location: 1)\n                        ]\n                    ),\n                    startPoint: startPoint,\n                    endPoint: endPoint\n                )\n                .opacity(\n                    colorScheme == .dark\n                    ? activeState == .inactive ? 0.25882353 : 1\n                    : activeState == .inactive ? 0.09803922 : 0.25882353\n                )\n            )\n            .allowsHitTesting(false)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/TabBar/Views/EditorHistoryMenus.swift",
    "content": "//\n//  EditorHistoryMenus.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/8/24.\n//\n\nimport SwiftUI\n\nstruct EditorHistoryMenus: View {\n    @EnvironmentObject private var editorManager: EditorManager\n    @EnvironmentObject private var editor: Editor\n\n    var body: some View {\n        Group {\n            Menu {\n                ForEach(\n                    Array(editor.history.dropFirst(editor.historyOffset+1).enumerated()),\n                    id: \\.offset\n                ) { index, file in\n                    Button {\n                        editorManager.activeEditor = editor\n                        editor.historyOffset += index + 1\n                    } label: {\n                        HStack {\n                            file.icon\n                            Text(file.name)\n                        }\n                    }\n                }\n            } label: {\n                Image(systemName: \"chevron.left\")\n                    .opacity(editor.historyOffset == editor.history.count - 1 || editor.history.isEmpty ? 0.5 : 1)\n                    .frame(height: EditorTabBarView.height - 2)\n                    .padding(.horizontal, 4)\n            } primaryAction: {\n                editorManager.activeEditor = editor\n                editor.goBackInHistory()\n            }\n            .disabled(editor.historyOffset == editor.history.count - 1 || editor.history.isEmpty)\n            .help(\"Navigate back\")\n\n            Menu {\n                ForEach(\n                    Array(editor.history.prefix(editor.historyOffset).reversed().enumerated()),\n                    id: \\.offset\n                ) { index, file in\n                    Button {\n                        editorManager.activeEditor = editor\n                        editor.historyOffset -= index + 1\n                    } label: {\n                        HStack {\n                            file.icon\n                            Text(file.name)\n                        }\n                    }\n                }\n            } label: {\n                Image(systemName: \"chevron.right\")\n                    .opacity(editor.historyOffset == 0 ? 0.5 : 1)\n                    .frame(height: EditorTabBarView.height - 2)\n                    .padding(.horizontal, 4)\n            } primaryAction: {\n                editorManager.activeEditor = editor\n                editor.goForwardInHistory()\n            }\n            .disabled(editor.historyOffset == 0)\n            .help(\"Navigate forward\")\n        }\n        .buttonStyle(.icon)\n        .controlSize(.small)\n        .font(EditorTabBarAccessoryIcon.iconFont)\n    }\n}\n\n#Preview {\n    EditorHistoryMenus()\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/TabBar/Views/EditorTabBarAccessory.swift",
    "content": "//\n//  TabBarAccessory.swift\n//  CodeEdit\n//\n//  Created by Lingxi Li on 4/28/22.\n//\n\nimport SwiftUI\n\n/// Accessory icon's view for tab bar.\nstruct EditorTabBarAccessoryIcon: View {\n    /// Unifies icon font for tab bar accessories.\n    static let iconFont = Font.system(size: 14, weight: .regular, design: .default)\n\n    private let icon: Image\n    private let isActive: Bool\n    private let action: () -> Void\n\n    init(icon: Image, isActive: Bool = false, action: @escaping () -> Void) {\n        self.icon = icon\n        self.isActive = isActive\n        self.action = action\n    }\n\n    var body: some View {\n        Button(\n            action: action,\n            label: {\n                icon\n            }\n        )\n        .buttonStyle(.icon(isActive: isActive, size: 24))\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/TabBar/Views/EditorTabBarContextMenu.swift",
    "content": "//\n//  EditorTabBarContextMenu.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 6/4/22.\n//\n\nimport SwiftUI\nimport Foundation\n\nextension View {\n    func tabBarContextMenu(item: CEWorkspaceFile, isTemporary: Bool) -> some View {\n        modifier(EditorTabBarContextMenu(item: item, isTemporary: isTemporary))\n    }\n}\n\nstruct EditorTabBarContextMenu: ViewModifier {\n    init(\n        item: CEWorkspaceFile,\n        isTemporary: Bool\n    ) {\n        self.item = item\n        self.isTemporary = isTemporary\n    }\n\n    @EnvironmentObject var workspace: WorkspaceDocument\n\n    @EnvironmentObject var tabs: Editor\n\n    @Environment(\\.splitEditor)\n    var splitEditor\n\n    private var item: CEWorkspaceFile\n    private var isTemporary: Bool\n\n    // swiftlint:disable:next function_body_length\n    func body(content: Content) -> some View {\n        content.contextMenu(menuItems: {\n            Group {\n                Button(\"Close Tab\") {\n                    withAnimation {\n                        tabs.closeTab(file: item)\n                    }\n                }\n                .keyboardShortcut(\"w\", modifiers: [.command])\n\n                Button(\"Close Other Tabs\") {\n                    withAnimation {\n                        tabs.tabs.map({ $0.file }).forEach { file in\n                            if file != item {\n                                tabs.closeTab(file: file)\n                            }\n                        }\n                    }\n                }\n\n                Button(\"Close Tabs to the Right\") {\n                    withAnimation {\n                        if let index = tabs.tabs.firstIndex(where: { $0.file == item }), index + 1 < tabs.tabs.count {\n                            tabs.tabs[(index + 1)...].forEach {\n                                tabs.closeTab(file: $0.file)\n                            }\n                        }\n                    }\n                }\n                // Disable this option when current tab is the last one.\n                .disabled(tabs.tabs.last?.file == item)\n\n                Button(\"Close All\") {\n                    withAnimation {\n                        tabs.tabs.forEach {\n                            tabs.closeTab(file: $0.file)\n                        }\n                    }\n                }\n\n                if isTemporary {\n                    Button(\"Keep Open\") {\n                        tabs.temporaryTab = nil\n                    }\n                }\n            }\n\n            Divider()\n\n            Group {\n                Button(\"Copy Path\") {\n                    copyPath(item: item)\n                }\n\n                Button(\"Copy Relative Path\") {\n                    copyRelativePath(item: item)\n                }\n            }\n\n            Divider()\n\n            Group {\n                Button(\"Show in Finder\") {\n                    item.showInFinder()\n                }\n\n                Button(\"Reveal in Project Navigator\") {\n                    workspace.listenerModel.highlightedFileItem = item\n                }\n\n                Button(\"Open in New Window\") {\n\n                }\n                .disabled(true)\n            }\n\n            Divider()\n\n            Button(\"Split Up\") {\n                moveToNewSplit(.top)\n            }\n            Button(\"Split Down\") {\n                moveToNewSplit(.bottom)\n            }\n            Button(\"Split Left\") {\n                moveToNewSplit(.leading)\n            }\n            Button(\"Split Right\") {\n                moveToNewSplit(.trailing)\n            }\n        })\n    }\n\n    // MARK: - Actions\n\n    /// Copies the absolute path of the given `FileItem`\n    /// - Parameter item: The `FileItem` to use.\n    private func copyPath(item: CEWorkspaceFile) {\n        NSPasteboard.general.clearContents()\n        NSPasteboard.general.setString(item.url.standardizedFileURL.path, forType: .string)\n    }\n\n    func moveToNewSplit(_ edge: Edge) {\n        let newEditor = Editor(files: [item], workspace: workspace)\n        splitEditor(edge, newEditor)\n        tabs.closeTab(file: item)\n        workspace.editorManager?.activeEditor = newEditor\n    }\n\n    /// Copies the relative path from the workspace folder to the given file item to the pasteboard.\n    /// - Parameter item: The `FileItem` to use.\n    private func copyRelativePath(item: CEWorkspaceFile) {\n        guard let rootPath = workspace.workspaceFileManager?.folderUrl else {\n            return\n        }\n        let destinationComponents = item.url.standardizedFileURL.pathComponents\n        let baseComponents = rootPath.standardizedFileURL.pathComponents\n\n        // Find common prefix length\n        var prefixCount = 0\n        while prefixCount < min(destinationComponents.count, baseComponents.count)\n                && destinationComponents[prefixCount] == baseComponents[prefixCount] {\n            prefixCount += 1\n        }\n        // Build the relative path\n        let upPath = String(repeating: \"../\", count: baseComponents.count - prefixCount)\n        let downPath = destinationComponents[prefixCount...].joined(separator: \"/\")\n\n        // Copy it to the clipboard\n        NSPasteboard.general.clearContents()\n        NSPasteboard.general.setString(upPath + downPath, forType: .string)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/TabBar/Views/EditorTabBarDivider.swift",
    "content": "//\n//  EditorTabBarDivider.swift\n//  CodeEdit\n//\n//  Created by Lingxi Li on 4/22/22.\n//\n\nimport SwiftUI\n\n/// The vertical divider between tab bar items.\nstruct EditorTabDivider: View {\n    @Environment(\\.colorScheme)\n    var colorScheme\n\n    let width: CGFloat = 1\n\n    var body: some View {\n        Rectangle()\n            .frame(width: width)\n            .padding(.vertical, 8)\n            .foregroundColor(\n                Color(nsColor: colorScheme == .dark ? .white : .black)\n                    .opacity(0.12)\n            )\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/TabBar/Views/EditorTabBarLeadingAccessories.swift",
    "content": "//\n//  EditorTabBarLeadingAccessories.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 9/7/23.\n//\n\nimport SwiftUI\n\nstruct EditorTabBarLeadingAccessories: View {\n    @Environment(\\.controlActiveState)\n    private var activeState\n\n    @EnvironmentObject private var editorManager: EditorManager\n    @EnvironmentObject private var editor: Editor\n\n    @State private var otherEditor: Editor?\n\n    var body: some View {\n        HStack(spacing: 0) {\n            if otherEditor != nil {\n                EditorTabBarAccessoryIcon(\n                    icon: .init(systemName: \"multiply\"),\n                    action: { [weak editor] in\n                        guard let editor else { return }\n                        editorManager.closeEditor(editor)\n                    }\n                )\n                .help(\"Close this Editor\")\n                .disabled(editorManager.isFocusingActiveEditor)\n                .opacity(editorManager.isFocusingActiveEditor ? 0.5 : 1)\n\n                EditorTabBarAccessoryIcon(\n                    icon: .init(\n                        systemName: editorManager.isFocusingActiveEditor\n                        ? \"arrow.down.forward.and.arrow.up.backward\"\n                        : \"arrow.up.left.and.arrow.down.right\"\n                    ),\n                    isActive: editorManager.isFocusingActiveEditor,\n                    action: {\n                        editorManager.toggleFocusingEditor(from: editor)\n                    }\n                )\n                .help(\n                    editorManager.isFocusingActiveEditor\n                    ? \"Unfocus this Editor\"\n                    : \"Focus this Editor\"\n                )\n\n                Divider()\n                    .frame(height: 10)\n                    .padding(.horizontal, 4)\n            }\n\n            EditorHistoryMenus()\n        }\n        .foregroundColor(.secondary)\n        .buttonStyle(.plain)\n        .padding(.horizontal, 5)\n        .opacity(activeState != .inactive ? 1.0 : 0.5)\n        .frame(maxHeight: .infinity) // Fill out vertical spaces.\n        .onAppear {\n            otherEditor = editorManager.editorLayout.findSomeEditor(except: editor)\n        }\n        .onReceive(editorManager.objectWillChange) { _ in\n            otherEditor = editorManager.editorLayout.findSomeEditor(except: editor)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/TabBar/Views/EditorTabBarTrailingAccessories.swift",
    "content": "//\n//  EditorTabBarTrailingAccessories.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 9/7/23.\n//\n\nimport SwiftUI\n\nstruct EditorTabBarTrailingAccessories: View {\n    @AppSettings(\\.textEditing.wrapLinesToEditorWidth)\n    var wrapLinesToEditorWidth\n    @AppSettings(\\.textEditing.showMinimap)\n    var showMinimap\n\n    @Environment(\\.splitEditor)\n    var splitEditor\n\n    @Environment(\\.modifierKeys)\n    var modifierKeys\n\n    @Environment(\\.controlActiveState)\n    private var activeState\n\n    @EnvironmentObject var workspace: WorkspaceDocument\n\n    @EnvironmentObject private var editorManager: EditorManager\n\n    @EnvironmentObject private var editor: Editor\n\n    @Binding var codeFile: CodeFileDocument?\n\n    var body: some View {\n        HStack(spacing: 6) {\n            // Once more options are implemented that are available for non-code documents, remove this if statement\n            if let codeFile {\n                editorOptionsMenu(codeFile: codeFile)\n                Divider()\n                    .padding(.vertical, 10)\n            }\n            splitviewButton\n        }\n        .buttonStyle(.icon)\n        .disabled(editorManager.isFocusingActiveEditor)\n        .opacity(editorManager.isFocusingActiveEditor ? 0.5 : 1)\n        .padding(.horizontal, 7)\n        .opacity(activeState != .inactive ? 1.0 : 0.5)\n        .frame(maxHeight: .infinity) // Fill out vertical spaces.\n    }\n\n    func editorOptionsMenu(codeFile: CodeFileDocument) -> some View {\n        // This is a button so it gets the same styling from the Group in `body`.\n        Button(action: {}, label: { Image(systemName: \"slider.horizontal.3\") })\n            .overlay {\n                Menu {\n                    Toggle(\"Show Minimap\", isOn: $showMinimap)\n                        .keyboardShortcut(\"M\", modifiers: [.command, .shift, .control])\n                    Divider()\n                    Toggle(\n                        \"Wrap Lines\",\n                        isOn: Binding(\n                            get: { [weak codeFile] in codeFile?.wrapLines ?? wrapLinesToEditorWidth },\n                            set: { [weak codeFile] in\n                                codeFile?.wrapLines = $0\n                            }\n                        )\n                    )\n                } label: {}\n                    .menuStyle(.borderlessButton)\n                    .menuIndicator(.hidden)\n            }\n    }\n\n    var splitviewButton: some View {\n        Group {\n            switch (editor.parent?.axis, modifierKeys.contains(.option)) {\n            case (.horizontal, true), (.vertical, false):\n                Button {\n                    split(edge: .bottom)\n                } label: {\n                    Image(symbol: \"square.split.horizontal.plus\")\n                }\n                .help(\"Split Vertically\")\n\n            case (.vertical, true), (.horizontal, false):\n                Button {\n                    split(edge: .trailing)\n                } label: {\n                    Image(symbol: \"square.split.vertical.plus\")\n                }\n                .help(\"Split Horizontally\")\n\n            default:\n                EmptyView()\n            }\n        }\n    }\n\n    func split(edge: Edge) {\n        let newEditor: Editor\n        if let tab = editor.selectedTab {\n            newEditor = .init(files: [tab], temporaryTab: tab, workspace: workspace)\n        } else {\n            newEditor = .init()\n        }\n        splitEditor(edge, newEditor)\n        editorManager.updateCachedFlattenedEditors = true\n        editorManager.activeEditor = newEditor\n    }\n}\n\nstruct TabBarTrailingAccessories_Previews: PreviewProvider {\n    static var previews: some View {\n        EditorTabBarTrailingAccessories(codeFile: .constant(nil))\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/TabBar/Views/EditorTabBarView.swift",
    "content": "//\n//  EditorTabBarView.swift\n//  CodeEdit\n//\n//  Created by Lukas Pistrol and Lingxi Li on 17.03.22.\n//\n\nimport SwiftUI\n\nstruct EditorTabBarView: View {\n    let hasTopInsets: Bool\n    @Binding var codeFile: CodeFileDocument?\n    /// The height of tab bar.\n    /// I am not making it a private variable because it may need to be used in outside views.\n    static let height = 28.0\n\n    var body: some View {\n        HStack(alignment: .center, spacing: 0) {\n            EditorTabBarLeadingAccessories()\n                .padding(.top, hasTopInsets ? -1 : 0)\n            EditorTabs()\n                .accessibilityElement(children: .contain)\n                .accessibilityLabel(\"Tab Bar\")\n                .accessibilityIdentifier(\"TabBar\")\n            EditorTabBarTrailingAccessories(codeFile: $codeFile)\n                .padding(.top, hasTopInsets ? -1 : 0)\n        }\n        .frame(height: EditorTabBarView.height - (hasTopInsets ? 1 : 0))\n        .clipped()\n        .padding(.leading, -1)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/Views/AnyFileView.swift",
    "content": "//\n//  AnyFileView.swift\n//  CodeEdit\n//\n//  Created by Paul Ebose on 2024/5/9.\n//\n\nimport SwiftUI\nimport QuickLookUI\n\n/// A view for previewing any kind of file.\n///\n/// ```swift\n/// AnyFileView(fileURL)\n/// ```\n/// If the file cannot be previewed, a file icon thumbnail is shown instead.\nstruct AnyFileView: NSViewRepresentable {\n\n    /// URL of the file to preview. You can pass in any file type.\n    private let fileURL: NSURL\n\n    init(_ fileURL: URL) {\n        self.fileURL = fileURL as NSURL\n    }\n\n    func makeNSView(context: Context) -> QLPreviewView {\n        let qlPreviewView = QLPreviewView()\n        qlPreviewView.previewItem = fileURL\n        qlPreviewView.shouldCloseWithWindow = false // Temp work around for something more reasonable.\n        return qlPreviewView\n    }\n\n    func updateNSView(_ qlPreviewView: QLPreviewView, context: Context) {\n        qlPreviewView.previewItem = fileURL\n    }\n\n    // Temp work around for something more reasonable.\n    // Open quickly should empty the results (but cache the query) when closed,\n    // and then re-search or recompute the results when re-opened.\n    static func dismantleNSView(_ qlPreviewView: QLPreviewView, coordinator: ()) {\n        qlPreviewView.close()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/Views/CodeFileView.swift",
    "content": "//\n//  CodeFileView.swift\n//  CodeEditModules/CodeFile\n//\n//  Created by Marco Carnevali on 17/03/22.\n//\n\nimport Foundation\nimport SwiftUI\nimport CodeEditSourceEditor\nimport CodeEditTextView\nimport CodeEditLanguages\nimport Combine\n\n/// CodeFileView is just a wrapper of the `CodeEditor` dependency\nstruct CodeFileView: View {\n    @ObservedObject private var editorInstance: EditorInstance\n    @ObservedObject private var codeFile: CodeFileDocument\n\n    @State private var treeSitterClient: TreeSitterClient = TreeSitterClient()\n\n    /// Any coordinators passed to the view.\n    private var textViewCoordinators: [TextViewCoordinator]\n    private var highlightProviders: [any HighlightProviding] = []\n\n    @AppSettings(\\.textEditing.defaultTabWidth)\n    var defaultTabWidth\n    @AppSettings(\\.textEditing.indentOption)\n    var indentOption\n    @AppSettings(\\.textEditing.lineHeightMultiple)\n    var lineHeightMultiple\n    @AppSettings(\\.textEditing.wrapLinesToEditorWidth)\n    var wrapLinesToEditorWidth\n    @AppSettings(\\.textEditing.overscroll)\n    var overscroll\n    @AppSettings(\\.textEditing.font)\n    var settingsFont\n    @AppSettings(\\.theme.useThemeBackground)\n    var useThemeBackground\n    @AppSettings(\\.theme.matchAppearance)\n    var matchAppearance\n    @AppSettings(\\.textEditing.letterSpacing)\n    var letterSpacing\n    @AppSettings(\\.textEditing.bracketEmphasis)\n    var bracketEmphasis\n    @AppSettings(\\.textEditing.useSystemCursor)\n    var useSystemCursor\n    @AppSettings(\\.textEditing.showGutter)\n    var showGutter\n    @AppSettings(\\.textEditing.showMinimap)\n    var showMinimap\n    @AppSettings(\\.textEditing.showFoldingRibbon)\n    var showFoldingRibbon\n    @AppSettings(\\.textEditing.reformatAtColumn)\n    var reformatAtColumn\n    @AppSettings(\\.textEditing.showReformattingGuide)\n    var showReformattingGuide\n    @AppSettings(\\.textEditing.invisibleCharacters)\n    var invisibleCharactersConfiguration\n    @AppSettings(\\.textEditing.warningCharacters)\n    var warningCharacters\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @EnvironmentObject var undoRegistration: UndoManagerRegistration\n\n    @ObservedObject private var themeModel: ThemeModel = .shared\n\n    @State private var treeSitter = TreeSitterClient()\n\n    private var cancellables = Set<AnyCancellable>()\n\n    private let isEditable: Bool\n\n    init(\n        editorInstance: EditorInstance,\n        codeFile: CodeFileDocument,\n        textViewCoordinators: [TextViewCoordinator] = [],\n        isEditable: Bool = true\n    ) {\n        self._editorInstance = .init(wrappedValue: editorInstance)\n        self._codeFile = .init(wrappedValue: codeFile)\n\n        self.textViewCoordinators = textViewCoordinators\n            + [editorInstance.rangeTranslator]\n            + [codeFile.contentCoordinator]\n            + [codeFile.languageServerObjects.textCoordinator]\n        self.isEditable = isEditable\n\n        if let openOptions = codeFile.openOptions {\n            codeFile.openOptions = nil\n            editorInstance.cursorPositions = openOptions.cursorPositions\n        }\n\n        highlightProviders = [codeFile.languageServerObjects.highlightProvider] + [treeSitterClient]\n\n        codeFile\n            .contentCoordinator\n            .textUpdatePublisher\n            .sink { [weak codeFile] _ in\n                codeFile?.updateChangeCount(.changeDone)\n            }\n            .store(in: &cancellables)\n    }\n\n    private var currentTheme: Theme {\n        themeModel.selectedTheme ?? themeModel.themes.first!\n    }\n\n    @State private var font: NSFont = Settings[\\.textEditing].font.current\n\n    @Environment(\\.edgeInsets)\n    private var edgeInsets\n\n    var body: some View {\n        SourceEditor(\n            codeFile.content ?? NSTextStorage(),\n            language: codeFile.getLanguage(),\n            configuration: SourceEditorConfiguration(\n                appearance: .init(\n                    theme: currentTheme.editor.editorTheme,\n                    useThemeBackground: useThemeBackground,\n                    font: font,\n                    lineHeightMultiple: lineHeightMultiple,\n                    letterSpacing: letterSpacing,\n                    wrapLines: wrapLinesToEditorWidth,\n                    useSystemCursor: useSystemCursor,\n                    tabWidth: defaultTabWidth,\n                    bracketPairEmphasis: getBracketPairEmphasis()\n                ),\n                behavior: .init(\n                    isEditable: isEditable,\n                    indentOption: indentOption.textViewOption(),\n                    reformatAtColumn: reformatAtColumn\n                ),\n                layout: .init(\n                    editorOverscroll: overscroll.overscrollPercentage,\n                    contentInsets: edgeInsets.nsEdgeInsets,\n                    additionalTextInsets: NSEdgeInsets(top: 2, left: 0, bottom: 0, right: 0)\n                ),\n                peripherals: .init(\n                    showGutter: showGutter,\n                    showMinimap: showMinimap,\n                    showReformattingGuide: showReformattingGuide,\n                    showFoldingRibbon: showFoldingRibbon,\n                    invisibleCharactersConfiguration: invisibleCharactersConfiguration.textViewOption(),\n                    warningCharacters: Set(warningCharacters.characters.keys)\n                )\n            ),\n            state: Binding(\n                get: {\n                    SourceEditorState(\n                        cursorPositions: editorInstance.cursorPositions,\n                        scrollPosition: editorInstance.scrollPosition,\n                        findText: editorInstance.findText,\n                        replaceText: editorInstance.replaceText\n                    )\n                },\n                set: { newState in\n                    editorInstance.cursorPositions = newState.cursorPositions ?? []\n                    editorInstance.scrollPosition = newState.scrollPosition\n                    editorInstance.findText = newState.findText\n                    editorInstance.findTextSubject.send(newState.findText)\n                    editorInstance.replaceText = newState.replaceText\n                    editorInstance.replaceTextSubject.send(newState.replaceText)\n                }\n            ),\n            highlightProviders: highlightProviders,\n            undoManager: undoRegistration.manager(forFile: editorInstance.file),\n            coordinators: textViewCoordinators\n        )\n        // This view needs to refresh when the codefile changes. The file URL is too stable.\n        .id(ObjectIdentifier(codeFile))\n        .background {\n            if colorScheme == .dark {\n                EffectView(.underPageBackground)\n            } else {\n                EffectView(.contentBackground)\n            }\n        }\n        .colorScheme(currentTheme.appearance == .dark ? .dark : .light)\n        // minHeight zero fixes a bug where the app would freeze if the contents of the file are empty.\n        .frame(minHeight: .zero, maxHeight: .infinity)\n        .onChange(of: settingsFont) { _, newFontSetting in\n            font = newFontSetting.current\n        }\n    }\n\n    /// Determines the style of bracket emphasis based on the `bracketEmphasis` setting and the current theme.\n    /// - Returns: The emphasis style to use for bracket pair emphasis.\n    private func getBracketPairEmphasis() -> BracketPairEmphasis? {\n        let color = if Settings[\\.textEditing].bracketEmphasis.useCustomColor {\n            Settings[\\.textEditing].bracketEmphasis.color.nsColor\n        } else {\n            currentTheme.editor.text.nsColor.withAlphaComponent(0.8)\n        }\n\n        switch Settings[\\.textEditing].bracketEmphasis.highlightType {\n        case .disabled:\n            return nil\n        case .flash:\n            return .flash\n        case .bordered:\n            return .bordered(color: color)\n        case .underline:\n            return .underline(color: color)\n        }\n    }\n}\n\n// This extension is kept here because it should not be used elsewhere in the app and may cause confusion\n// due to the similar type name from the CETV module.\nprivate extension SettingsData.TextEditingSettings.IndentOption {\n    func textViewOption() -> IndentOption {\n        switch self.indentType {\n        case .spaces:\n            return IndentOption.spaces(count: spaceCount)\n        case .tab:\n            return IndentOption.tab\n        }\n    }\n}\n\nprivate extension SettingsData.TextEditingSettings.InvisibleCharactersConfig {\n    func textViewOption() -> InvisibleCharactersConfiguration {\n        guard self.enabled else { return .empty }\n        var config = InvisibleCharactersConfiguration(\n            showSpaces: self.showSpaces,\n            showTabs: self.showTabs,\n            showLineEndings: self.showLineEndings\n        )\n\n        config.spaceReplacement = self.spaceReplacement\n        config.tabReplacement = self.tabReplacement\n        config.carriageReturnReplacement = self.carriageReturnReplacement\n        config.lineFeedReplacement = self.lineFeedReplacement\n        config.paragraphSeparatorReplacement = self.paragraphSeparatorReplacement\n        config.lineSeparatorReplacement = self.lineSeparatorReplacement\n\n        return config\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/Views/EditorAreaFileView.swift",
    "content": "//\n//  EditorAreaFileView.swift\n//  CodeEdit\n//\n//  Created by Pavel Kasila on 20.03.22.\n//\n\nimport AppKit\nimport AVKit\nimport CodeEditSourceEditor\nimport SwiftUI\n\nstruct EditorAreaFileView: View {\n\n    @EnvironmentObject private var editorManager: EditorManager\n    @EnvironmentObject private var editor: Editor\n    @EnvironmentObject private var statusBarViewModel: StatusBarViewModel\n\n    @Environment(\\.edgeInsets)\n    private var edgeInsets\n\n    var editorInstance: EditorInstance\n    var codeFile: CodeFileDocument\n\n    @ViewBuilder var editorAreaFileView: some View {\n        if let utType = codeFile.utType, utType.conforms(to: .text) {\n            CodeFileView(\n                editorInstance: editorInstance,\n                codeFile: codeFile\n            )\n        } else {\n            NonTextFileView(fileDocument: codeFile)\n                .padding(.top, edgeInsets.top - 1.74)\n                .padding(.bottom, StatusBarView.height + 1.26)\n                .modifier(UpdateStatusBarInfo(with: codeFile.fileURL))\n                .onDisappear {\n                    statusBarViewModel.dimensions = nil\n                    statusBarViewModel.fileSize = nil\n                }\n        }\n    }\n\n    var body: some View {\n        editorAreaFileView\n            .frame(maxWidth: .infinity, maxHeight: .infinity)\n            .onHover { hover in\n                DispatchQueue.main.async {\n                    if hover {\n                        NSCursor.iBeam.push()\n                    } else {\n                        NSCursor.pop()\n                    }\n                }\n            }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/Views/EditorAreaView.swift",
    "content": "//\n//  EditorAreaView.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 16/02/2023.\n//\n\nimport SwiftUI\nimport CodeEditTextView\nimport UniformTypeIdentifiers\n\nstruct EditorAreaView: View {\n    @AppSettings(\\.general.showEditorJumpBar)\n    var showEditorJumpBar\n\n    @AppSettings(\\.navigation.navigationStyle)\n    var navigationStyle\n\n    @AppSettings(\\.general.dimEditorsWithoutFocus)\n    var dimEditorsWithoutFocus\n\n    @ObservedObject var editor: Editor\n\n    @FocusState.Binding var focus: Editor?\n\n    @EnvironmentObject private var editorManager: EditorManager\n\n    @State var codeFile: (() -> CodeFileDocument?)?\n\n    @Environment(\\.window.value)\n    private var window: NSWindow?\n\n    @Environment(\\.isEditorLayoutAtEdge)\n    private var isAtEdge\n\n    init(editor: Editor, focus: FocusState<Editor?>.Binding) {\n        self.editor = editor\n        self._focus = focus\n        if let file = editor.selectedTab?.file.fileDocument {\n            self.codeFile = { [weak file] in file }\n        }\n    }\n\n    var body: some View {\n        var shouldShowTabBar: Bool {\n            return navigationStyle == .openInTabs\n            || editorManager.flattenedEditors.contains { editor in\n                (editor.temporaryTab == nil && !editor.tabs.isEmpty)\n                || (editor.temporaryTab != nil && editor.tabs.count > 1)\n            }\n        }\n\n        var editorInsetAmount: Double {\n            let tabBarHeight = shouldShowTabBar ? (EditorTabBarView.height) : 0\n            let jumpBarHeight = showEditorJumpBar ? (EditorJumpBarView.height) : 0\n            return tabBarHeight + jumpBarHeight\n        }\n\n        VStack {\n            if let selected = editor.selectedTab {\n                if let codeFile = codeFile?() {\n                    EditorAreaFileView(editorInstance: selected, codeFile: codeFile)\n                        .focusedObject(editor)\n                        .transformEnvironment(\\.edgeInsets) { insets in\n                            insets.top += editorInsetAmount\n                        }\n                        .opacity(dimEditorsWithoutFocus && editor != editorManager.activeEditor ? 0.5 : 1)\n                        .onDrop(of: [.fileURL], isTargeted: nil) { providers in\n                            _ = handleDrop(providers: providers)\n                            return true\n                        }\n                } else {\n                    LoadingFileView(selected.file.name)\n                        .onAppear {\n                            if let file = selected.file.fileDocument {\n                                self.codeFile = { [weak file] in file }\n                            }\n                        }\n                        .onReceive(selected.file.fileDocumentPublisher) { latestValue in\n                            self.codeFile = { [weak latestValue] in latestValue }\n                        }\n                }\n            } else {\n                CEContentUnavailableView(\"No Editor\")\n                    .padding(.top, editorInsetAmount)\n                    .onTapGesture {\n                        editorManager.activeEditor = editor\n                    }\n                    .onDrop(of: [.fileURL], isTargeted: nil) { providers in\n                        _ = handleDrop(providers: providers)\n                        return true\n                    }\n            }\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n        .ignoresSafeArea(.all)\n        .safeAreaInset(edge: .top, spacing: 0) {\n            GeometryReader { geometry in\n                let topSafeArea = geometry.safeAreaInsets.top\n                let fileBinding = Binding {\n                    codeFile?()\n                } set: { newFile in\n                    codeFile = { [weak newFile] in newFile }\n                }\n\n                VStack(spacing: 0) {\n                    if isAtEdge != .top, #available(macOS 26, *) {\n                        Spacer().frame(height: 4)\n                    }\n\n                    if topSafeArea > 0 {\n                        Rectangle()\n                            .fill(.clear)\n                            .frame(height: 1)\n                            .background(.clear)\n                    }\n                    if shouldShowTabBar {\n                        EditorTabBarView(hasTopInsets: topSafeArea > 0, codeFile: fileBinding)\n                            .id(\"TabBarView\" + editor.id.uuidString)\n                            .environmentObject(editor)\n                        if #unavailable(macOS 26) {\n                            Divider()\n                        }\n                    }\n                    if showEditorJumpBar {\n                        EditorJumpBarView(\n                            file: editor.selectedTab?.file,\n                            shouldShowTabBar: shouldShowTabBar,\n                            codeFile: fileBinding\n                        ) { [weak editor] newFile in\n                            if let file = editor?.selectedTab, let index = editor?.tabs.firstIndex(of: file) {\n                                editor?.openTab(file: newFile, at: index)\n                            }\n                        }\n                        .environmentObject(editor)\n                        .padding(.top, shouldShowTabBar ? -1 : 0)\n                        if #unavailable(macOS 26) {\n                            Divider()\n                        }\n                    }\n                    // On Tahoe we only show one divider\n                    if #available(macOS 26, *), shouldShowTabBar || showEditorJumpBar {\n                        Divider()\n                    }\n                }\n                .environment(\\.isActiveEditor, editor == editorManager.activeEditor)\n                .if(.tahoe) {\n                    // FB20047271: Glass toolbar effect ignores floating scroll view views.\n                    // https://openradar.appspot.com/radar?id=EhAKBVJhZGFyEICAgKbGmesJ\n\n                    // FB20191516: Can't disable backgrounded liquid glass tint\n                    // https://openradar.appspot.com/radar?id=EhAKBVJhZGFyEICAgLqTk-4J\n                    // Tracking Issue: #2191\n                    // Add this to the top:\n                    // ```\n                    // @AppSettings(\\.theme.useThemeBackground)\n                    // var useThemeBackground\n                    //\n                    // private var backgroundColor: NSColor {\n                    //     let fallback = NSColor.textBackgroundColor\n                    //     return if useThemeBackground {\n                    //         ThemeModel.shared.selectedTheme?.editor.background.nsColor ?? fallback\n                    //     } else {\n                    //         fallback\n                    //     }\n                    // }\n                    // ```\n                    // And use this:\n                    // ```\n                    // $0.background(\n                    //    Rectangle().fill(.clear)\n                    //        .glassEffect(.regular.tint(Color(backgroundColor))\n                    //        .ignoresSafeArea(.all)\n                    // )\n                    // ```\n                    // When we can figure out how to disable the 'not focused' glass effect.\n\n                    $0.background(EffectView(.headerView).ignoresSafeArea(.all))\n                } else: {\n                    $0.background(EffectView(.headerView))\n                }\n            }\n        }\n        .focused($focus, equals: editor)\n        // Fixing this is causing a malloc exception when a file is edited & closed. See #1886\n//        .onReceive(NotificationCenter.default.publisher(for: TextView.textDidChangeNotification)) { _ in\n//            if navigationStyle == .openInTabs {\n//                editor.temporaryTab = nil\n//            }\n//        }\n        .onChange(of: navigationStyle) { _, newValue in\n            if newValue == .openInPlace && editor.tabs.count == 1 {\n                editor.temporaryTab = editor.tabs[0]\n            }\n        }\n        .onChange(of: editor.selectedTab) { _, newValue in\n            if let file = newValue?.file.fileDocument {\n                codeFile = { [weak file] in file }\n            }\n        }\n    }\n\n    private func handleDrop(providers: [NSItemProvider]) -> Bool {\n        for provider in providers {\n            provider.loadItem(forTypeIdentifier: UTType.fileURL.identifier, options: nil) { item, _ in\n                guard let data = item as? Data,\n                      let url = URL(dataRepresentation: data, relativeTo: nil) else {\n                    return\n                }\n\n                DispatchQueue.main.async {\n                    let file = CEWorkspaceFile(url: url)\n                    editorManager.activeEditor = editor\n                    editor.openTab(file: file)\n                }\n            }\n        }\n        return true\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/Views/EditorLayoutView.swift",
    "content": "//\n//  EditorLayoutView.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 20/02/2023.\n//\n\nimport SwiftUI\n\nstruct EditorLayoutView: View {\n    var layout: EditorLayout\n\n    @FocusState.Binding var focus: Editor?\n\n    @Environment(\\.window.value)\n    private var window\n\n    @Environment(\\.isEditorLayoutAtEdge)\n    private var isAtEdge\n\n    var toolbarHeight: CGFloat {\n        window?.contentView?.safeAreaInsets.top ?? .zero\n    }\n\n    var body: some View {\n        VStack {\n            switch layout {\n            case .one(let detailEditor):\n                EditorAreaView(editor: detailEditor, focus: $focus)\n                    .transformEnvironment(\\.edgeInsets) { insets in\n                        switch isAtEdge {\n                        case .all:\n                            insets.top += toolbarHeight\n                            insets.bottom += StatusBarView.height + 5\n                        case .top:\n                            insets.top += toolbarHeight\n                        case .bottom:\n                            insets.bottom += StatusBarView.height + 5\n                        default:\n                            return\n                        }\n                    }\n            case .vertical(let data), .horizontal(let data):\n                SubEditorLayoutView(data: data, focus: $focus)\n            }\n        }\n    }\n\n    struct SubEditorLayoutView: View {\n        @Environment(\\.colorScheme)\n        private var colorScheme\n\n        @ObservedObject var data: SplitViewData\n        @FocusState.Binding var focus: Editor?\n\n        var body: some View {\n            SplitView(axis: data.axis, dividerStyle: .editorDivider) {\n                splitView\n            }\n            .edgesIgnoringSafeArea([.top, .bottom])\n        }\n\n        var splitView: some View {\n            ForEach(Array(data.editorLayouts.enumerated()), id: \\.offset) { index, item in\n                EditorLayoutView(layout: item, focus: $focus)\n                   .transformEnvironment(\\.isEditorLayoutAtEdge) { belowToolbar in\n                       calcIsAtEdge(current: &belowToolbar, index: index)\n                   }\n                   .environment(\\.splitEditor) { [weak data] edge, newEditor in\n                       data?.split(edge, at: index, new: newEditor)\n                   }\n            }\n        }\n\n        func calcIsAtEdge(current: inout VerticalEdge.Set, index: Int) {\n            if case .vertical = data.axis {\n                guard data.editorLayouts.count != 1 else { return }\n                if index == data.editorLayouts.count - 1 {\n                    current.remove(.top)\n                } else if index == 0 {\n                    current.remove(.bottom)\n                } else {\n                    current = []\n                }\n            }\n        }\n    }\n}\n\nstruct BelowToolbarEnvironmentKey: EnvironmentKey {\n    static var defaultValue: VerticalEdge.Set = .all\n}\n\nextension EnvironmentValues {\n    var isEditorLayoutAtEdge: BelowToolbarEnvironmentKey.Value {\n        get { self[BelowToolbarEnvironmentKey.self] }\n        set { self[BelowToolbarEnvironmentKey.self] = newValue }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/Views/ImageFileView.swift",
    "content": "//\n//  ImageFileView.swift\n//  CodeEdit\n//\n//  Created by Paul Ebose on 2024/5/9.\n//\n\nimport SwiftUI\n\n/// A view for previewing an image, while respecting its dimensions.\n///\n/// It receives a URL to an image file and attempts to preview it.\n///\n/// ```swift\n/// ImageFileView(imageURL)\n/// ```\n/// This implementation allows for proper image scaling, especially when the image dimensions are smaller than\n/// the size of the image view area.\n///\n/// If the preview image cannot be created, it shows a  *\"Cannot preview image\"* text.\nstruct ImageFileView: View {\n\n    /// URL of the image you want to preview.\n    private let imageURL: URL\n\n    init(_ imageURL: URL) {\n        self.imageURL = imageURL\n    }\n\n    var body: some View {\n        if let nsImage = NSImage(contentsOf: imageURL),\n           let imageReps = nsImage.representations.first {\n\n            let pixelWidth = CGFloat(imageReps.pixelsWide)\n            let pixelHeight = CGFloat(imageReps.pixelsHigh)\n\n            GeometryReader { proxy in\n                ZStack {\n                    AnyFileView(imageURL)\n                        .frame(\n                            maxWidth: min(pixelWidth, proxy.size.width, nsImage.size.width),\n                            maxHeight: min(pixelHeight, proxy.size.height, nsImage.size.height)\n                        )\n\n                }\n                .frame(width: proxy.size.width, height: proxy.size.height)\n            }\n        } else {\n            Text(\"Cannot preview image\")\n        }\n    }\n\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/Views/LoadingFileView.swift",
    "content": "//\n//  LoadingFileView.swift\n//  CodeEdit\n//\n//  Created by Paul Ebose on 2024/5/9.\n//\n\nimport SwiftUI\n\n/// A placeholder view that shows a spinner and text.\n///\n/// It optionally receives a file name.\n/// ```swift\n/// LoadingFileView(filename)\n/// LoadingFileView()\n/// ```\nstruct LoadingFileView: View {\n\n    /// Name of file that is about to open.\n    private var filename = \"\"\n\n    init(_ filename: String = \"\") {\n        self.filename = filename\n    }\n\n    var body: some View {\n        VStack(spacing: 10) {\n            Spacer()\n            ProgressView()\n            Text(\"Opening \\(filename)...\")\n            Spacer()\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/Views/NonTextFileView.swift",
    "content": "//\n//  NonTextFileView.swift\n//  CodeEdit\n//\n//  Created by Paul Ebose on 2024/5/10.\n//\n\nimport SwiftUI\n\n/// Determines what type of file is passed in, and previews it accordingly.\n///\n/// ```swift\n/// NonTextFileView(fileDocument)\n/// ```\nstruct NonTextFileView: View {\n\n    /// The file document you wish to open.\n    let fileDocument: CodeFileDocument\n\n    var body: some View {\n        Group {\n            if let fileURL = fileDocument.fileURL {\n\n                if let utType = fileDocument.utType {\n                    if utType.conforms(to: .image) {\n                        ImageFileView(fileURL)\n                    } else if utType.conforms(to: .pdf) {\n                        PDFFileView(fileURL)\n                    } else {\n                        AnyFileView(fileURL)\n                    }\n                } else {\n                    AnyFileView(fileURL)\n                }\n            } else {\n                ZStack {\n                    Text(\"Cannot retrieve URL to the file you opened.\")\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/Views/PDFFileView.swift",
    "content": "//\n//  PDFFileView.swift\n//  CodeEdit\n//\n//  Created by Paul Ebose on 2024/5/7.\n//\n\nimport SwiftUI\nimport PDFKit.PDFView\n\n/// A view for previewing a PDF file.\n///\n/// It takes in a file URL and attempts to preview a PDF.\n///\n/// ```swift\n/// PDFFileView(fileURL)\n/// ```\n///\n/// This view provides a context menu that is the same as the one in the native MacOS Preview\n/// application, for PDF files.\n///\n/// This view also allows for proper scaling of the PDF.\n///\n/// - Note: If the file located at the `fileURL` cannot be previewed as a PDF, nothing happens, no redraw or anything.\nstruct PDFFileView: NSViewRepresentable {\n\n    /// URL of the PDF file you want to preview.\n    private let fileURL: URL\n\n    init(_ fileURL: URL) {\n        self.fileURL = fileURL\n    }\n\n    func makeNSView(context: Context) -> PDFView {\n        let pdfView = attachPDFDocumentToView(PDFView())\n        return pdfView\n    }\n\n    func updateNSView(_ pdfView: PDFView, context: Context) {\n        attachPDFDocumentToView(pdfView)\n    }\n\n    /// Creates a PDF document using ``PDFFileView`` `.fileUrl`, and attaches it to the passed in `pdfView`.\n    /// - Parameters:\n    ///   - pdfView: The [`PDFView`](https://developer.apple.com/documentation/pdfkit/pdfview) you wish to modify.\n    /// - Returns: A modified `pdfView` if a valid PDF was created, or an unmodified `pdfView` if it could not create a\n    /// valid PDF.\n    @discardableResult\n    private func attachPDFDocumentToView (_ pdfView: PDFView) -> PDFView {\n        guard let pdfDocument = PDFDocument(url: fileURL) else {\n            // What can happen is the view doesn't redraw, so whatever was in the editor area view remains as is.\n            return pdfView\n        }\n        pdfView.document = pdfDocument\n        pdfView.backgroundColor = NSColor.controlBackgroundColor\n        return pdfView\n    }\n\n}\n"
  },
  {
    "path": "CodeEdit/Features/Editor/Views/WindowCodeFileView.swift",
    "content": "//\n//  WindowCodeFileView.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 3/19/23.\n//\n\nimport Foundation\nimport SwiftUI\n\n/// View that fixes [#1158](https://github.com/CodeEditApp/CodeEdit/issues/1158)\n/// # Should **not** be used other than in a single file window.\nstruct WindowCodeFileView: View {\n    @StateObject var editorInstance: EditorInstance\n    @StateObject var undoRegistration: UndoManagerRegistration = UndoManagerRegistration()\n    var codeFile: CodeFileDocument\n\n    init(codeFile: CodeFileDocument) {\n        self._editorInstance = .init(\n            wrappedValue: EditorInstance(\n                workspace: nil,\n                file: CEWorkspaceFile(url: codeFile.fileURL ?? URL(fileURLWithPath: \"\"))\n            )\n        )\n        self.codeFile = codeFile\n    }\n\n    var body: some View {\n        if let utType = codeFile.utType, utType.conforms(to: .text) {\n            CodeFileView(editorInstance: editorInstance, codeFile: codeFile)\n                .environmentObject(undoRegistration)\n        } else {\n            NonTextFileView(fileDocument: codeFile)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Extensions/Commands+ForEach.swift",
    "content": "//\n//  Commands+ForEach.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 11/03/2023.\n//\n\nimport SwiftUI\n\n// A custom ForEach struct is created instead of using the SwiftUI.ForEach one,\n// as we can't create a new initializer due to a swift limitation.\n// Instead, this CommandsForEach struct is used, which functions equally.\n\n/// A structure that builds commandmenus on demand from an underlying collection of data.\n/// Maximum 10 items are supported.\nstruct CommandsForEach<Data: RandomAccessCollection, Content: Commands>: Commands where Data.Index == Int {\n\n    var data: Data\n\n    var content: (Data.Element) -> Content\n\n    init(_ data: Data, @CommandsBuilder content: @escaping (Data.Element) -> Content) {\n        self.data = data\n        self.content = content\n    }\n\n    var body: some Commands {\n        switch data.count {\n        case 0:\n            EmptyCommands()\n        case 1:\n            CommandsBuilder.buildBlock(content(data[0]))\n        case 2:\n            CommandsBuilder.buildBlock(content(data[0]), content(data[1]))\n        case 3:\n            CommandsBuilder.buildBlock(content(data[0]), content(data[1]), content(data[2]))\n        case 4:\n            CommandsBuilder.buildBlock(content(data[0]), content(data[1]), content(data[2]), content(data[3]))\n        case 5:\n            CommandsBuilder.buildBlock(\n                content(data[0]),\n                content(data[1]),\n                content(data[2]),\n                content(data[3]),\n                content(data[4])\n            )\n        case 6:\n            CommandsBuilder.buildBlock(\n                content(data[0]),\n                content(data[1]),\n                content(data[2]),\n                content(data[3]),\n                content(data[4]),\n                content(data[5])\n            )\n        case 7:\n            CommandsBuilder.buildBlock(\n                content(data[0]),\n                content(data[1]),\n                content(data[2]),\n                content(data[3]),\n                content(data[4]),\n                content(data[5]),\n                content(data[6])\n            )\n        case 8:\n            CommandsBuilder.buildBlock(\n                content(data[0]),\n                content(data[1]),\n                content(data[2]),\n                content(data[3]),\n                content(data[4]),\n                content(data[5]),\n                content(data[6]),\n                content(data[7])\n            )\n        case 9:\n            CommandsBuilder.buildBlock(\n                content(data[0]),\n                content(data[1]),\n                content(data[2]),\n                content(data[3]),\n                content(data[4]),\n                content(data[5]),\n                content(data[6]),\n                content(data[7]),\n                content(data[8])\n            )\n        default:\n            CommandsBuilder.buildBlock(\n                content(data[0]),\n                content(data[1]),\n                content(data[2]),\n                content(data[3]),\n                content(data[4]),\n                content(data[5]),\n                content(data[6]),\n                content(data[7]),\n                content(data[8]),\n                content(data[9])\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Extensions/ExtensionActivatorView.swift",
    "content": "//\n//  ExtensionActivatorView.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 30/12/2022.\n//\n\nimport SwiftUI\nimport ExtensionKit\n\nstruct ExtensionActivatorView: NSViewControllerRepresentable {\n    func makeNSViewController(context: Context) -> EXAppExtensionBrowserViewController {\n        EXAppExtensionBrowserViewController()\n    }\n\n    func updateNSViewController(_ nsViewController: EXAppExtensionBrowserViewController, context: Context) {\n\n    }\n\n    func makeCoordinator() {\n\n    }\n}\n\nstruct ExtensionActivatorView_Previews: PreviewProvider {\n    static var previews: some View {\n        ExtensionActivatorView()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Extensions/ExtensionDetailView.swift",
    "content": "//\n//  ExtensionDetailView.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 01/01/2023.\n//\n\nimport SwiftUI\n\nstruct ExtensionDetailView: View {\n    var ext: ExtensionInfo\n\n    var body: some View {\n        VStack(alignment: .leading) {\n            HStack {\n                if let icon = ext.icon {\n                    Image(nsImage: icon)\n                        .resizable()\n                        .frame(width: 150, height: 150)\n                }\n\n                Form {\n                    Section(\"Features\") {\n                        ForEach(ext.availableFeatures, id: \\.self) { feature in\n                            Text(feature.description)\n                        }\n                    }\n                }\n                .formStyle(.grouped)\n            }\n\n            Text(\"Extension Settings\")\n                .font(.title3)\n                .fontWeight(.semibold)\n                .padding(.leading)\n            ExtensionSceneView(with: ext.endpoint, sceneID: \"Settings\")\n                .padding(.top, -5)\n                .ceEnvironment(\\.complexValue, [\"HAllo\"])\n        }\n        .navigationSubtitle(ext.name)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Extensions/ExtensionDiscovery.swift",
    "content": "//\n//  ExtensionDiscovery.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 31/12/2022.\n//\n\nimport Foundation\nimport ExtensionFoundation\nimport CollectionConcurrencyKit\nimport GRDB\n\n/// Discovery of available extension endpoints.\nfinal class ExtensionDiscovery: ObservableObject {\n    /// Shared instance of this class.\n    static var shared = ExtensionDiscovery()\n\n    /// Endpoint used by extensions.\n    static var endPointIdentifier = \"codeedit.extension\"\n\n    private static var dbURL = URL.libraryDirectory\n        .appending(path: \"Preferences/com.apple.LaunchServices/com.apple.LaunchServices.SettingsStore.sql\")\n\n    /// Publishes a list of extension endpoints approved by the user.\n    /// These endpoints can be used to create new extension processes with XPC.\n    @Published var extensions: [ExtensionInfo] = []\n\n    private var discoverTask: Task<Void, Never>?\n    private var availabilityTask: Task<Void, Never>?\n\n    // Init is private as only 1 instance of this class may (needs to) exist.\n    private init() {\n        // Two separate tasks need to be used, as the awaits never finish.\n        discoverTask = discover()\n        availabilityTask = availabilityOverview()\n    }\n\n    /// Discover all the extensions approved by the user. Updates `extensions` when an extension gets enabled/disabled.\n    /// Warning: This function will continue to run and won't return. Therefore, it should be ran in a separate `Task`.\n    private func discover() -> Task<Void, Never> {\n        Task { [weak self] in\n            do {\n                let sequence = try AppExtensionIdentity.matching(appExtensionPointIDs: Self.endPointIdentifier)\n\n                for await endpoints in sequence {\n                    guard !Task.isCancelled && self != nil else { return }\n                    await self?.updateExtensions(endpoints: endpoints, shouldRestartExisting: true)\n                }\n            } catch {\n                print(\"Error while searching for extensions: \\(error.localizedDescription)\")\n            }\n        }\n    }\n\n    private func updateExtensions(endpoints: [AppExtensionIdentity], shouldRestartExisting: Bool = false) async {\n        let extensions = await endpoints.concurrentCompactMap {\n            try? await ExtensionInfo(endpoint: $0)\n        }\n\n        await MainActor.run {\n            self.extensions = extensions\n        }\n\n        if shouldRestartExisting {\n            self.extensions.filter(\\.isDebug)\n                .forEach {\n                    print(\"Restarting \\($0.name)...\")\n                    $0.restart()\n                }\n        }\n    }\n\n    /// Observes extensions available on the system, and reports if extensions are disabled.\n    /// These extensions must be enabled by the user first, before they can be discovered by `discover`.\n    /// Warning: This function will continue to run and won't return. Therefore, it should be ran in a separate `Task`.\n    private func availabilityOverview() -> Task<Void, Never> {\n        Task { [weak self] in\n            for await availability in AppExtensionIdentity.availabilityUpdates {\n                guard !Task.isCancelled && self != nil else { return }\n                do {\n                    if availability.disabledCount > 0 {\n                        print(\"Found \\(availability.disabledCount) disabled extensions, trying to activate...\")\n                        try await self?.activateDisabledExtensions()\n                    }\n\n                    if availability.unapprovedCount > 0 {\n                        print(\"Found \\(availability.disabledCount) unapproved extensions, trying to activate...\")\n\n                        let identifiers = [(\"com.tweety.TestCodeEdit.AutoActivatedExtension\", \"2MMGJGVTB4\")]\n                        try await self?.activateUnapprovedExtensions(with: identifiers)\n                    }\n\n                    let sequence = try AppExtensionIdentity.matching(appExtensionPointIDs: Self.endPointIdentifier)\n\n                    let extensions = await sequence.first { _ in true }\n\n                    guard let extensions else { return }\n                    await self?.updateExtensions(endpoints: extensions)\n                } catch {\n                    print(\"Could not auto-activate extensions.\")\n                }\n            }\n        }\n    }\n\n    struct SettingsStoreRecord: Codable, TableRecord, FetchableRecord, PersistableRecord {\n        var identifier: String\n        var timestamp: String\n        var userElection: Int\n\n        static var databaseTableName: String = \"Election\"\n    }\n\n    private func activateDisabledExtensions() async throws {\n        let dbQueue = try DatabaseQueue(path: Self.dbURL.path())\n\n        return try await dbQueue.write {\n            try SettingsStoreRecord\n                .filter(Column(\"identifier\").like(\"%\\(Self.endPointIdentifier)%\"))\n                .filter(Column(\"userElection\") == 2)\n                .updateAll($0, Column(\"userElection\") -= 1)\n        }\n    }\n\n    private func activateUnapprovedExtensions(with identifiers: [(bundleID: String, devID: String)]) async throws {\n        let dbQueue = try DatabaseQueue(path: Self.dbURL.path())\n\n        return try await dbQueue.write { table in\n            try identifiers.map { identifier in\n                SettingsStoreRecord(\n                    // swiftlint:disable:next line_length\n                    identifier: \"\\(Bundle.main.bundleIdentifier!)::\\(Self.endPointIdentifier):\\(identifier.bundleID):\\(identifier.devID)\",\n                    timestamp: String(Date.now.description.dropLast(6)),\n                    userElection: 1\n                )\n            }.forEach {\n                try $0.save(table)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Extensions/ExtensionInfo.swift",
    "content": "//\n//  ExtensionInfo.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 31/12/2022.\n//\n\nimport AppKit\nimport CodeEditKit\nimport ExtensionFoundation\n\nstruct ExtensionInfo: Identifiable, Hashable {\n\n    let endpoint: AppExtensionIdentity\n\n    let availableFeatures: [ExtensionKind]\n\n    let isDebug: Bool\n\n    var bundleURL: URL\n\n    var bundle: Bundle?\n\n    var pid: Int32\n\n    var id: String {\n        endpoint.bundleIdentifier\n    }\n\n    var name: String {\n        endpoint.localizedName\n    }\n\n    var version: String? {\n        bundle?.infoDictionary?[\"CFBundleShortVersionString\"] as? String\n    }\n\n    func restart() {\n        kill(pid, SIGKILL)\n    }\n\n    init(endpoint: AppExtensionIdentity) async throws {\n        self.endpoint = endpoint\n\n        let process = try await AppExtensionProcess(configuration: .init(appExtensionIdentity: endpoint))\n\n        let connection = try process.makeXPCConnection()\n        connection.remoteObjectInterface = .init(with: XPCWrappable.self)\n        connection.resume()\n\n        defer {\n            connection.invalidate()\n        }\n\n        self.pid = try await ExtensionInfo.getProcessID(connection)\n        self.isDebug = try await ExtensionInfo.getDebugState(connection)\n        self.availableFeatures = try await ExtensionInfo.getAvailableFeatures(connection)\n        self.bundleURL = try await ExtensionInfo.getBundleURL(connection)\n        self.bundle = Bundle(url: bundleURL)\n    }\n}\n\n// Functions to get basic information about extension\nextension ExtensionInfo {\n    private static func getProcessID(_ connection: NSXPCConnection) async throws -> pid_t {\n        try await connection.withContinuation { (service: XPCWrappable, continuation) in\n            service.getExtensionProcessIdentifier {\n                continuation.resumingHandler($0, .none)\n            }\n        }\n    }\n\n    private static func getDebugState(_ connection: NSXPCConnection) async throws -> Bool {\n        try await connection.withContinuation { (service: XPCWrappable, continuation) in\n            service.isDebug {\n                continuation.resumingHandler($0, .none)\n            }\n        }\n    }\n\n    private static func getAvailableFeatures(_ connection: NSXPCConnection) async throws -> [ExtensionKind] {\n        let encodedAvailableFeatures = try await connection.withContinuation { (service: XPCWrappable, continuation) in\n            service.getExtensionKinds(reply: continuation.resumingHandler)\n        }\n        return try JSONDecoder().decode([ExtensionKind].self, from: encodedAvailableFeatures)\n    }\n\n    private static func getBundleURL(_ connection: NSXPCConnection) async throws -> URL {\n        let bundleURLEncoded = try await connection.withContinuation { (service: XPCWrappable, continuation) in\n            service.getExtensionURL(reply: continuation.resumingHandler)\n        }\n\n        return try JSONDecoder().decode(URL.self, from: bundleURLEncoded)\n    }\n}\n\nextension ExtensionInfo {\n    /// Bundle identifier of parent app\n    var parentBundleIdentifier: String {\n        endpoint.bundleIdentifier.split(separator: \".\").dropLast().joined(separator: \".\")\n    }\n\n    var lastPathOfBundleIdentifier: String {\n        String(endpoint.bundleIdentifier.split(separator: \".\").last!)\n    }\n\n    /// Icon of appex folder\n    public var icon: NSImage? {\n        // TODO: Use icon of extension instead of parent app\n        // A way to get the path of an .appex file should be used.\n        // Unfortunately, NSWorkspace.shared.urlForApplication only seems to work for .app\n        let path = NSWorkspace.shared.urlForApplication(withBundleIdentifier: parentBundleIdentifier)\n        guard let path else { return nil }\n\n        return NSWorkspace.shared.icon(forFile: path.path)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Extensions/ExtensionManagerWindow.swift",
    "content": "//\n//  ExtensionManagerWindow.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 24/03/2023.\n//\n\nimport SwiftUI\n\nstruct ExtensionManagerWindow: Scene {\n    @ObservedObject var manager = ExtensionManager.shared\n\n    @State var selection = Set<ExtensionInfo>()\n\n    var body: some Scene {\n        Window(\"Extensions\", id: SceneID.extensions.rawValue) {\n            NavigationSplitView {\n                ExtensionsListView(selection: $selection)\n            } detail: {\n                switch selection.count {\n                case 0:\n                    Text(\"Select an extension\")\n                case 1:\n                    ExtensionDetailView(ext: selection.first!)\n                default:\n                    Text(\"\\(selection.count) selected\")\n                }\n            }\n            .environmentObject(manager)\n            .focusedObject(manager)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Extensions/ExtensionSceneView.swift",
    "content": "//\n//  ExtensionSceneView.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 31/12/2022.\n//\n\nimport SwiftUI\nimport CodeEditKit\nimport ExtensionKit\nimport ExtensionFoundation\n\nstruct ExtensionSceneView: NSViewControllerRepresentable {\n\n    @Environment(\\.openWindow)\n    var openWindow\n\n    let appExtension: AppExtensionIdentity\n    let sceneID: String\n\n    init(with appExtension: AppExtensionIdentity, sceneID: String) {\n        self.appExtension = appExtension\n        self.sceneID = sceneID\n    }\n\n    func makeNSViewController(context: Context) -> EXHostViewController {\n        let controller = EXHostViewController()\n        controller.delegate = context.coordinator\n        controller.configuration = .some(.init(appExtension: appExtension, sceneID: sceneID))\n        context.coordinator.updateEnvironment(context.environment._ceEnvironment)\n        return controller\n    }\n\n    func updateNSViewController(_ nsViewController: EXHostViewController, context: Context) {\n        nsViewController.configuration = .init(appExtension: appExtension, sceneID: sceneID)\n        context.coordinator.updateEnvironment(context.environment._ceEnvironment)\n    }\n\n    func makeCoordinator() -> Coordinator {\n        Coordinator { id in\n            print(id)\n            DispatchQueue.main.async {\n                openWindow(id: id)\n            }\n        }\n    }\n\n    class Coordinator: NSObject, EXHostViewControllerDelegate, EnvironmentPublisherObjc {\n        var isOnline: Bool = false\n        var toPublish: Data?\n        var openWindow: (String) -> Void\n\n        init(openWindow: @escaping (String) -> Void) {\n            self.openWindow = openWindow\n        }\n\n        var connection: NSXPCConnection?\n\n        func publishEnvironment(data: Data) {\n            guard let decodedCallbacks = try? JSONDecoder().decode(Callbacks.self, from: data) else { return }\n            switch decodedCallbacks {\n            case .openWindow(let id):\n                openWindow(id)\n            }\n        }\n\n        func updateEnvironment(_ value: _CEEnvironment) {\n            guard let newEnvironmentData = try? JSONEncoder().encode(value) else { return }\n\n            guard isOnline else {\n                toPublish = newEnvironmentData\n                return\n            }\n\n            Task {\n                do {\n                    try await connection!.withService { (service: EnvironmentPublisherObjc) in\n                        service.publishEnvironment(data: newEnvironmentData)\n                    }\n                } catch {\n                    print(error)\n                }\n            }\n        }\n\n        func hostViewControllerWillDeactivate(_ viewController: EXHostViewController, error: Error?) {\n            isOnline = false\n            print(\"Host will deactivate\", error as Any)\n        }\n\n        func hostViewControllerDidActivate(_ viewController: EXHostViewController) {\n            isOnline = true\n            do {\n                self.connection = try viewController.makeXPCConnection()\n                connection?.exportedInterface = .init(with: EnvironmentPublisherObjc.self)\n                connection?.exportedObject = self\n                connection?.remoteObjectInterface = .init(with: EnvironmentPublisherObjc.self)\n                connection?.resume()\n                if let toPublish {\n                    Task {\n                        try? await connection?.withService { (service: EnvironmentPublisherObjc) in\n                            service.publishEnvironment(data: toPublish)\n                        }\n                    }\n                }\n            } catch {\n                print(\"Unable to create connection: \\(String(describing: error))\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Extensions/ExtensionsListView.swift",
    "content": "//\n//  ExtensionsListView.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 24/03/2023.\n//\n\nimport SwiftUI\n\nstruct ExtensionsListView: View {\n    @EnvironmentObject var manager: ExtensionManager\n    @Binding var selection: Set<ExtensionInfo>\n    @State var showActivatorView = false\n\n    var body: some View {\n        List(manager.extensions, id: \\.self, selection: $selection) { ext in\n            HStack {\n                if let icon = ext.icon {\n                    Image(nsImage: icon)\n                        .resizable()\n                        .aspectRatio(1, contentMode: .fit)\n                }\n\n                VStack(alignment: .leading) {\n                    Text(ext.name)\n                    if let version = ext.version {\n                        Text(version)\n                            .font(.footnote)\n                            .foregroundColor(.secondary)\n                    }\n                }\n            }\n            .frame(height: 40)\n        }\n\n        .toolbar {\n            Toggle(isOn: $showActivatorView) {\n                Image(systemName: \"puzzlepiece.extension\")\n            }\n            .toggleStyle(.button)\n            .popover(isPresented: $showActivatorView) {\n                ExtensionActivatorView()\n                    .frame(width: 400, height: 300)\n            }\n        }\n        .onChange(of: manager.extensions) { oldValue, newValue in\n            // Select the first one if previously the extension list was empty.\n            if oldValue.isEmpty, let first = newValue.first {\n                selection = [first]\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Extensions/ExtensionsManager.swift",
    "content": "//\n//  ExtensionManager.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 30/12/2022.\n//\n\nimport Foundation\nimport SwiftUI\nimport ExtensionFoundation\nimport CodeEditKit\nimport ConcurrencyPlus\n\nfinal class ExtensionManager: ObservableObject {\n\n    static var shared = ExtensionManager()\n\n    @Published var extensions: [ExtensionInfo] = []\n\n    init() {\n        ExtensionDiscovery.shared.$extensions.assign(to: &$extensions)\n    }\n\n}\n"
  },
  {
    "path": "CodeEdit/Features/Extensions/codeedit.extension.appextensionpoint",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>codeedit.extension</key>\n    <dict>\n        <key>EXPresentsUserInterface</key>\n        <true/>\n    </dict>\n</dict>\n</plist>\n"
  },
  {
    "path": "CodeEdit/Features/Feedback/Controllers/FeedbackWindowController.swift",
    "content": "//\n//  FeedbackWindowController.swift\n//  CodeEditModules/Feedback\n//\n//  Created by Nanashi Li on 2022/04/14.\n//\n\nimport SwiftUI\n\nfinal class FeedbackWindowController: NSWindowController, NSToolbarDelegate {\n    convenience init<T: View>(view: T, size: NSSize) {\n        let hostingController = NSHostingController(rootView: SettingsInjector { view })\n        let window = NSWindow(contentViewController: hostingController)\n        self.init(window: window)\n        window.title = \"Feedback for CodeEdit\"\n        window.setContentSize(size)\n        window.styleMask.insert(.fullSizeContentView)\n        window.styleMask.remove(.resizable)\n    }\n\n    override func showWindow(_ sender: Any?) {\n        window?.center()\n        window?.alphaValue = 0.0\n\n        super.showWindow(sender)\n\n        window?.animator().alphaValue = 1.0\n\n        window?.collectionBehavior = [.transient, .ignoresCycle]\n        window?.backgroundColor = .windowBackgroundColor\n\n        feedbackToolbar()\n    }\n\n    private func feedbackToolbar() {\n        let toolbar = NSToolbar(identifier: UUID().uuidString)\n        toolbar.delegate = self\n        toolbar.displayMode = .labelOnly\n        self.window?.toolbarStyle = .unifiedCompact\n        self.window?.toolbar = toolbar\n    }\n\n    func closeAnimated() {\n        NSAnimationContext.beginGrouping()\n        NSAnimationContext.current.duration = 0.4\n        NSAnimationContext.current.completionHandler = {\n            self.close()\n        }\n        window?.animator().alphaValue = 0.0\n        NSAnimationContext.endGrouping()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Feedback/FeedbackView.swift",
    "content": "//\n//  FeedbackView.swift\n//  CodeEditModules/Feedback\n//\n//  Created by Nanashi Li on 2022/04/14.\n//\n\nimport SwiftUI\n\nstruct FeedbackView: View {\n    @ObservedObject private var feedbackModel: FeedbackModel = .shared\n\n    @State var showsAlert: Bool = false\n\n    @State var isSubmitButtonPressed: Bool = false\n\n    var body: some View {\n        VStack {\n            ScrollView {\n                VStack(alignment: .leading) {\n                    basicInformation\n                    description\n                }\n                .padding(.horizontal, 90)\n                .padding(.vertical, 30)\n            }\n            FeedbackToolbar {\n                HelpButton(action: {})\n                Spacer()\n                if feedbackModel.isSubmitted {\n                    Text(\"Feedback submitted\")\n                } else if feedbackModel.failedToSubmit {\n                    Text(\"Failed to submit feedback\")\n                }\n                Button {\n                    feedbackModel.createIssue(\n                        title: feedbackModel.feedbackTitle,\n                        description: feedbackModel.issueDescription,\n                        steps: feedbackModel.stepsReproduceDescription,\n                        expectation: feedbackModel.expectationDescription,\n                        actuallyHappened: feedbackModel.whatHappenedDescription\n                    )\n                    isSubmitButtonPressed = true\n                } label: {\n                    Text(\"Submit\")\n                }\n                .alert(isPresented: self.$showsAlert) {\n                    Alert(\n                        title: Text(\"No GitHub Account\"),\n                        message: Text(\"A GitHub account is required to submit feedback.\"),\n                        primaryButton: .default(Text(\"Cancel\")),\n                        secondaryButton: .default(Text(\"Add Account\"))\n                    )\n                }\n            }\n            .padding(10)\n            .border(Color(NSColor.separatorColor))\n        }\n        .frame(width: 1028, height: 762)\n    }\n\n    private var basicInformation: some View {\n        VStack(alignment: .leading) {\n            Text(\"Basic Information\")\n                .fontWeight(.bold)\n                .font(.system(size: 20))\n\n            VStack(alignment: .leading) {\n                HStack {\n                    if isSubmitButtonPressed && feedbackModel.feedbackTitle.isEmpty {\n                        HStack {\n                            Image(systemName: \"arrow.right.circle.fill\")\n                                .foregroundColor(.red)\n                            Text(\"Please provide a descriptive title for your feedback:\")\n                        }.padding(.leading, -23)\n                    } else {\n                        Text(\"Please provide a descriptive title for your feedback:\")\n                    }\n                }\n                TextField(\"\", text: $feedbackModel.feedbackTitle)\n                Text(\"Example: CodeEdit crashes when using autocomplete\")\n                    .font(.system(size: 10))\n                    .foregroundColor(.secondary)\n            }\n            .padding(.top, -5)\n\n            VStack(alignment: .leading) {\n                HStack {\n                    if isSubmitButtonPressed && feedbackModel.issueAreaListSelection == \"none\" {\n                        HStack {\n                            Image(systemName: \"arrow.right.circle.fill\")\n                                .foregroundColor(.red)\n                            Text(\"Which area are you seeing an issue with?\")\n                        }.padding(.leading, -23)\n                    } else {\n                        Text(\"Which area are you seeing an issue with?\")\n                    }\n                }\n                Picker(\"\", selection: $feedbackModel.issueAreaListSelection) {\n                    ForEach(feedbackModel.issueAreaList) {\n                        if feedbackModel.issueAreaListSelection == \"none\" {\n                            Text($0.name)\n                                .tag($0.id)\n                                .foregroundColor(.secondary)\n                        } else {\n                            Text($0.name).tag($0.id)\n                        }\n                    }\n                }\n                .frame(width: 350)\n                .labelsHidden()\n            }\n            .padding(.top)\n\n            VStack(alignment: .leading) {\n                if isSubmitButtonPressed && feedbackModel.feedbackTypeListSelection == \"none\" {\n                    HStack {\n                        Image(systemName: \"arrow.right.circle.fill\")\n                            .foregroundColor(.red)\n                        Text(\"What type of feedback are you reporting?\")\n                    }.padding(.leading, -23)\n                } else {\n                    Text(\"What type of feedback are you reporting?\")\n                }\n                Picker(\"\", selection: $feedbackModel.feedbackTypeListSelection) {\n                    ForEach(feedbackModel.feedbackTypeList) {\n                        if feedbackModel.feedbackTypeListSelection == \"none\" {\n                            Text($0.name)\n                                .tag($0.id)\n                                .foregroundColor(.secondary)\n                        } else {\n                            Text($0.name).tag($0.id)\n                        }\n                    }\n                }\n                .frame(width: 350)\n                .labelsHidden()\n            }\n            .padding(.top)\n        }\n    }\n\n    private var description: some View {\n        VStack(alignment: .leading) {\n            Text(\"Description\")\n                .fontWeight(.bold)\n                .font(.system(size: 20))\n                .padding(.top)\n\n            VStack(alignment: .leading) {\n                HStack {\n                    if isSubmitButtonPressed && feedbackModel.issueDescription.isEmpty {\n                        HStack {\n                            Image(systemName: \"arrow.right.circle.fill\")\n                                .foregroundColor(.red)\n                            Text(\"Please describe the issue:\")\n                        }.padding(.leading, -23)\n                    } else {\n                        Text(\"Please describe the issue:\")\n                    }\n                }\n                TextEditor(text: $feedbackModel.issueDescription)\n                           .frame(minHeight: 127, alignment: .leading)\n                           .border(Color(NSColor.separatorColor))\n                Text(\"Example: CodeEdit crashes when the autocomplete popup appears on screen.\")\n                    .font(.system(size: 10))\n                    .foregroundColor(.secondary)\n            }\n            .padding(.top, -5)\n\n            VStack(alignment: .leading) {\n                Text(\"Please list the steps you took to reproduce the issue:\")\n                TextEditor(text: $feedbackModel.stepsReproduceDescription)\n                           .frame(minHeight: 60, alignment: .leading)\n                           .border(Color(NSColor.separatorColor))\n                Text(\"Example:\")\n                    .font(.system(size: 10))\n                    .foregroundColor(.secondary)\n                Text(\"1. Open the attached sample project\")\n                    .font(.system(size: 10))\n                    .foregroundColor(.secondary)\n                Text(\"2. type #import and wait for autocompletion to begin\")\n                    .font(.system(size: 10))\n                    .foregroundColor(.secondary)\n            }\n            .padding(.top)\n\n            VStack(alignment: .leading) {\n                Text(\"What did you expect to happen?\")\n                TextEditor(text: $feedbackModel.expectationDescription)\n                           .frame(minHeight: 60, alignment: .leading)\n                           .border(Color(NSColor.separatorColor))\n                Text(\"Example: I expected autocomplete to show me a list of headers.\")\n                    .font(.system(size: 10))\n                    .foregroundColor(.secondary)\n            }\n            .padding(.top)\n\n            VStack(alignment: .leading) {\n                Text(\"What actually happened?\")\n                TextEditor(text: $feedbackModel.whatHappenedDescription)\n                           .frame(minHeight: 60, alignment: .leading)\n                           .border(Color(NSColor.separatorColor))\n                // swiftlint:disable:next line_length\n                Text(\"Example: The autocomplete window flickered on screen and CodeEdit crashed. See attached crashlog.\")\n                    .font(.system(size: 10))\n                    .foregroundColor(.secondary)\n            }\n            .padding(.top)\n        }\n    }\n\n    func showWindow() {\n        FeedbackWindowController(view: self, size: NSSize(width: 1028, height: 762)).showWindow(nil)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Feedback/HelperView/FeedbackToolbar.swift",
    "content": "//\n//  FeedbackToolbar.swift\n//  CodeEditModules/Feedback\n//\n//  Created by Nanashi Li on 2022/04/14.\n//\n\nimport SwiftUI\n\nstruct FeedbackToolbar<T: View>: View {\n\n    private var content: () -> T\n\n    init(\n        bgColor: Color = Color(NSColor.controlBackgroundColor),\n        @ViewBuilder content: @escaping () -> T\n    ) {\n        self.content = content\n    }\n\n    var body: some View {\n        ZStack {\n            HStack {\n                content()\n                    .padding(.horizontal, 8)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Feedback/Model/FeedbackIssueArea.swift",
    "content": "//\n//  FeedbackIssueArea.swift\n//  CodeEditModules/Feedback\n//\n//  Created by Nanashi Li on 2022/04/14.\n//\n\nimport Foundation\n\nstruct FeedbackIssueArea: Identifiable, Hashable {\n    let name: String\n    let id: String\n}\n"
  },
  {
    "path": "CodeEdit/Features/Feedback/Model/FeedbackModel.swift",
    "content": "//\n//  FeedbackModel.swift\n//  CodeEditModules/Feedback\n//\n//  Created by Nanashi Li on 2022/04/14.\n//\n\nimport SwiftUI\n\npublic class FeedbackModel: ObservableObject {\n\n    public static let shared: FeedbackModel = .init()\n\n    private let keychain = CodeEditKeychain()\n\n    @Environment(\\.openURL)\n    var openIssueURL\n\n    @Published var isSubmitted: Bool = false\n    @Published var failedToSubmit: Bool = false\n    @Published var feedbackTitle: String = \"\"\n    @Published var issueDescription: String = \"\"\n    @Published var stepsReproduceDescription: String = \"\"\n    @Published var expectationDescription: String = \"\"\n    @Published var whatHappenedDescription: String = \"\"\n    @Published var issueAreaListSelection: FeedbackIssueArea.ID = \"none\"\n    @Published var feedbackTypeListSelection: FeedbackType.ID = \"none\"\n\n    @Published var feedbackTypeList = [\n        FeedbackType(name: \"Choose...\", id: \"none\"),\n        FeedbackType(name: \"Incorrect/Unexpected Behaviour\", id: \"behaviour\"),\n        FeedbackType(name: \"Application Crash\", id: \"crash\"),\n        FeedbackType(name: \"Application Slow/Unresponsive\", id: \"unresponsive\"),\n        FeedbackType(name: \"Suggestion\", id: \"suggestions\"),\n        FeedbackType(name: \"Other\", id: \"other\")\n    ]\n\n    @Published var issueAreaList = [\n        FeedbackIssueArea(name: \"Please select the problem area\", id: \"none\"),\n        FeedbackIssueArea(name: \"Project Navigator\", id: \"projectNavigator\"),\n        FeedbackIssueArea(name: \"Extensions\", id: \"extensions\"),\n        FeedbackIssueArea(name: \"Git\", id: \"git\"),\n        FeedbackIssueArea(name: \"Debugger\", id: \"debugger\"),\n        FeedbackIssueArea(name: \"Editor\", id: \"editor\"),\n        FeedbackIssueArea(name: \"Other\", id: \"other\")\n    ]\n\n    /// Gets the ID of the selected issue type and then\n    /// cross references it to select the right Label based on the type\n    private func getIssueLabel() -> String {\n        switch issueAreaListSelection {\n        case \"projectNavigator\":\n            return \"Project Navigator\"\n        case \"extensions\":\n            return \"Extensions\"\n        case \"git\":\n            return \"Git\"\n        case \"debugger\":\n            return \"Debugger\"\n        case \"editor\":\n            return \"Editor\"\n        case \"other\":\n            return \"Other\"\n        default:\n            return \"Other\"\n        }\n    }\n\n    /// This is just temporary till we have bot that will handle this\n    private func getFeedbackTypeTitle() -> String {\n        switch feedbackTypeListSelection {\n        case \"behaviour\":\n            return \"🐞\"\n        case \"crash\":\n            return \"🐞\"\n        case \"unresponsive\":\n            return \"🐞\"\n        case \"suggestions\":\n            return \"✨\"\n        case \"other\":\n            return \"📬\"\n        default:\n            return \"Other\"\n        }\n    }\n\n    /// Gets the ID of the selected feedback type and then\n    /// cross references it to select the right Label based on the type\n    private func getFeedbackTypeLabel() -> String {\n        switch feedbackTypeListSelection {\n        case \"behaviour\":\n            return \"Bug\"\n        case \"crash\":\n            return \"Bug\"\n        case \"unresponsive\":\n            return \"Bug\"\n        case \"suggestions\":\n            return \"Suggestion\"\n        case \"other\":\n            return \"Feedback\"\n        default:\n            return \"Other\"\n        }\n    }\n\n    /// The format for the issue body is how it will be displayed on\n    /// repos issues. If any changes are made use markdown format\n    /// because the text gets converted when created.\n    private func createIssueBody(\n        description: String,\n        steps: String?,\n        expectation: String?,\n        actuallyHappened: String?\n    ) -> String {\n        \"\"\"\n        **Description**\n\n        \\(description)\n\n        **Steps to Reproduce**\n\n        \\(steps ?? \"N/A\")\n\n        **What did you expect to happen?**\n\n        \\(expectation ?? \"N/A\")\n\n        **What actually happened?**\n\n        \\(actuallyHappened ?? \"N/A\")\n        \"\"\"\n    }\n\n    public func createIssue(\n        title: String,\n        description: String,\n        steps: String?,\n        expectation: String?,\n        actuallyHappened: String?\n    ) {\n        let gitAccounts = Settings[\\.accounts].sourceControlAccounts.gitAccounts\n        let firstGitAccount = gitAccounts.first\n\n        let config = GitHubTokenConfiguration(keychain.get(firstGitAccount!.name))\n        GitHubAccount(config).postIssue(\n            owner: \"CodeEditApp\",\n            repository: \"CodeEdit\",\n            title: \"\\(getFeedbackTypeTitle()) \\(title)\",\n            body: createIssueBody(\n                description: description,\n                steps: steps,\n                expectation: expectation,\n                actuallyHappened: actuallyHappened\n            ),\n            assignee: \"\",\n            labels: [getFeedbackTypeLabel(), getIssueLabel()]\n        ) { response in\n            switch response {\n            case .success(let issue):\n                if Settings[\\.sourceControl].general.openFeedbackInBrowser {\n                    self.openIssueURL(issue.htmlURL ?? URL(string: \"https://github.com/CodeEditApp/CodeEdit/issues\")!)\n                }\n                self.isSubmitted.toggle()\n                print(issue)\n            case .failure(let error):\n                self.failedToSubmit.toggle()\n                print(error)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Feedback/Model/FeedbackType.swift",
    "content": "//\n//  FeedbackType.swift\n//  CodeEditModules/Feedback\n//\n//  Created by Nanashi Li on 2022/04/14.\n//\n\nimport Foundation\n\nstruct FeedbackType: Identifiable, Hashable {\n    let name: String\n    let id: String\n}\n"
  },
  {
    "path": "CodeEdit/Features/InspectorArea/FileInspector/FileInspectorView.swift",
    "content": "//\n//  FileInspectorView.swift\n//  CodeEdit\n//\n//  Created by Nanashi Li on 2022/03/24.\n//\nimport SwiftUI\nimport CodeEditLanguages\n\nstruct FileInspectorView: View {\n    @EnvironmentObject private var workspace: WorkspaceDocument\n\n    @EnvironmentObject private var editorManager: EditorManager\n\n    @AppSettings(\\.textEditing)\n    private var textEditing\n\n    @State private var file: CEWorkspaceFile?\n\n    @State private var fileName: String = \"\"\n\n    // File settings overrides\n\n    @State private var language: CodeLanguage?\n\n    @State var indentOption: SettingsData.TextEditingSettings.IndentOption = .init(indentType: .tab)\n\n    @State var defaultTabWidth: Int = 0\n\n    @State var wrapLines: Bool = false\n\n    func updateFileOptions(_ textEditingOverride: SettingsData.TextEditingSettings? = nil) {\n        let textEditingSettings = textEditingOverride ?? textEditing\n        indentOption = file?.fileDocument?.indentOption ?? textEditingSettings.indentOption\n        defaultTabWidth = file?.fileDocument?.defaultTabWidth ?? textEditingSettings.defaultTabWidth\n        wrapLines = file?.fileDocument?.wrapLines ?? textEditingSettings.wrapLinesToEditorWidth\n    }\n\n    func updateInspectorSource() {\n        file = editorManager.activeEditor.selectedTab?.file\n        fileName = file?.name ?? \"\"\n        language = file?.fileDocument?.language\n        updateFileOptions()\n    }\n\n    var body: some View {\n        Group {\n            if file != nil {\n                Form {\n                    Section(\"Identity and Type\") {\n                        fileNameField\n                        fileType\n                    }\n                    Section {\n                        location\n                    }\n                    Section(\"Text Settings\") {\n                        indentUsing\n                        widthOptions\n                        wrapLinesToggle\n                    }\n                }\n            } else {\n                NoSelectionInspectorView()\n            }\n        }\n        .onAppear {\n            updateInspectorSource()\n        }\n        .onReceive(editorManager.activeEditor.objectWillChange) { _ in\n            updateInspectorSource()\n        }\n        .onChange(of: editorManager.activeEditor) { _, _ in\n            updateInspectorSource()\n        }\n        .onChange(of: editorManager.activeEditor.selectedTab) { _, _ in\n            updateInspectorSource()\n        }\n        .onChange(of: textEditing) { _, newValue in\n            updateFileOptions(newValue)\n        }\n    }\n\n    @ViewBuilder private var fileNameField: some View {\n        if let file {\n            TextField(\"Name\", text: $fileName)\n                .background(\n                    fileName != file.fileName() && !file.validateFileName(for: fileName) ? Color(errorRed) : Color.clear\n                )\n                .onSubmit {\n                    if file.validateFileName(for: fileName) {\n                        let destinationURL = file.url\n                            .deletingLastPathComponent()\n                            .appending(path: fileName)\n                        DispatchQueue.main.async { [weak workspace] in\n                            do {\n                                if let newItem = try workspace?.workspaceFileManager?.move(\n                                    file: file,\n                                    to: destinationURL\n                                ),\n                                   !newItem.isFolder {\n                                    editorManager.editorLayout.closeAllTabs(of: file)\n                                    editorManager.openTab(item: newItem)\n                                }\n                            } catch {\n                                let alert = NSAlert(error: error)\n                                alert.addButton(withTitle: \"Dismiss\")\n                                alert.runModal()\n                            }\n                        }\n                    } else {\n                        fileName = file.labelFileName()\n                    }\n                }\n        }\n    }\n\n    @ViewBuilder private var fileType: some View {\n        Picker(\n            \"Type\",\n            selection: $language\n        ) {\n            Text(\"Default - Detected\").tag(nil as CodeLanguage?)\n            Divider()\n            ForEach(CodeLanguage.allLanguages, id: \\.id) { language in\n                Text(language.id.rawValue.capitalized).tag(language as CodeLanguage?)\n            }\n        }\n        .onChange(of: language) { _, newValue in\n            file?.fileDocument?.language = newValue\n        }\n    }\n\n    private var location: some View {\n        Group {\n            if let file {\n                LabeledContent(\"Location\") {\n                    Button(\"Choose...\") {\n                        guard let newURL = chooseNewFileLocation() else {\n                            return\n                        }\n                        // This is ugly but if the tab is opened at the same time as closing the others, it doesn't open\n                        // And if the files are re-built at the same time as the tab is opened, it causes a memory error\n                        DispatchQueue.main.async { [weak workspace] in\n                            do {\n                                guard let newItem = try workspace?.workspaceFileManager?.move(file: file, to: newURL),\n                                      !newItem.isFolder else {\n                                    return\n                                }\n                                editorManager.editorLayout.closeAllTabs(of: file)\n                                editorManager.openTab(item: newItem)\n                            } catch {\n                                let alert = NSAlert(error: error)\n                                alert.addButton(withTitle: \"Dismiss\")\n                                alert.runModal()\n                            }\n                        }\n                    }\n                }\n                ExternalLink(showInFinder: true, destination: file.url) {\n                    Text(file.url.path(percentEncoded: false))\n                        .font(.footnote)\n                        .foregroundColor(.secondary)\n                }\n            }\n        }\n    }\n\n    private var indentUsing: some View {\n        Picker(\"Indent using\", selection: $indentOption.indentType) {\n            Text(\"Spaces\").tag(SettingsData.TextEditingSettings.IndentOption.IndentType.spaces)\n            Text(\"Tabs\").tag(SettingsData.TextEditingSettings.IndentOption.IndentType.tab)\n        }\n        .onChange(of: indentOption) { _, newValue in\n            file?.fileDocument?.indentOption = newValue == textEditing.indentOption ? nil : newValue\n        }\n    }\n\n    private var widthOptions: some View {\n        LabeledContent(\"Widths\") {\n            HStack(spacing: 5) {\n                VStack(alignment: .center, spacing: 0) {\n                    Stepper(\n                        \"\",\n                        value: Binding<Double>(\n                            get: { Double(defaultTabWidth) },\n                            set: { defaultTabWidth = Int($0) }\n                        ),\n                        in: 1...16,\n                        step: 1,\n                        format: .number\n                    )\n                    .labelsHidden()\n                    Text(\"Tab\")\n                        .foregroundColor(.primary)\n                        .font(.footnote)\n                }\n                .help(\"The visual width of tab characters\")\n                VStack(alignment: .center, spacing: 0) {\n                    Stepper(\n                        \"\",\n                        value: Binding<Double>(\n                            get: { Double(indentOption.spaceCount) },\n                            set: { indentOption.spaceCount = Int($0) }\n                        ),\n                        in: 1...10,\n                        step: 1,\n                        format: .number\n                    )\n                    .labelsHidden()\n                    Text(\"Indent\")\n                        .foregroundColor(.primary)\n                        .font(.footnote)\n                }\n                .help(\"The number of spaces to insert when the tab key is pressed.\")\n            }\n        }\n        .onChange(of: defaultTabWidth) { _, newValue in\n            file?.fileDocument?.defaultTabWidth = newValue == textEditing.defaultTabWidth ? nil : newValue\n        }\n    }\n\n    private var wrapLinesToggle: some View {\n        Toggle(\"Wrap lines\", isOn: $wrapLines)\n            .onChange(of: wrapLines) { _, newValue in\n                file?.fileDocument?.wrapLines = newValue == textEditing.wrapLinesToEditorWidth ? nil : newValue\n            }\n    }\n\n    private func chooseNewFileLocation() -> URL? {\n        guard let file else { return nil }\n        let dialogue = NSSavePanel()\n        dialogue.title = \"Save File\"\n        dialogue.directoryURL = file.url.deletingLastPathComponent()\n        dialogue.nameFieldStringValue = file.name\n        if dialogue.runModal() == .OK {\n            return dialogue.url\n        } else {\n            return nil\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/InspectorArea/HistoryInspector/HistoryInspectorItemView.swift",
    "content": "//\n//  HistoryInspectorItemView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 12/27/23.\n//\n\nimport SwiftUI\n\nstruct HistoryInspectorItemView: View {\n    var commit: GitCommit\n\n    @Binding var selection: GitCommit?\n\n    private var showPopup: Binding<Bool> {\n        Binding<Bool> {\n            selection == commit\n        } set: { newValue in\n            if newValue {\n                selection = commit\n            } else if selection == commit {\n                selection = nil\n            }\n        }\n    }\n\n    var body: some View {\n        CommitListItemView(commit: commit, showRef: false)\n            .instantPopover(isPresented: showPopup, arrowEdge: .leading) {\n                HistoryPopoverView(commit: commit)\n            }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/InspectorArea/HistoryInspector/HistoryInspectorModel.swift",
    "content": "//\n//  HistoryInspectorModel.swift\n//  CodeEdit\n//\n//  Created by Nanashi Li on 2022/04/18.\n//\n\nimport Foundation\n\nfinal class HistoryInspectorModel: ObservableObject {\n    private(set) var sourceControlManager: SourceControlManager?\n\n    /// The base URL of the workspace\n    private(set) var workspaceURL: URL?\n\n    /// The base URL of the workspace\n    private(set) var fileURL: String?\n\n    /// The selected branch from the GitClient\n    @Published var commitHistory: [GitCommit] = []\n\n    func setWorkspace(sourceControlManager: SourceControlManager?) async {\n        self.sourceControlManager = sourceControlManager\n        await updateCommitHistory()\n    }\n\n    func setFile(url: String?) async {\n        if fileURL != url {\n            fileURL = url\n            await updateCommitHistory()\n        }\n    }\n\n    func updateCommitHistory() async {\n        guard let sourceControlManager, let fileURL else {\n            await setCommitHistory([])\n            return\n        }\n\n        do {\n            let commitHistory = try await sourceControlManager\n                .gitClient\n                .getCommitHistory(\n                    maxCount: 40,\n                    fileLocalPath: fileURL,\n                    showMergeCommits: Settings.shared.preferences.sourceControl.git.showMergeCommitsPerFileLog\n                )\n            await setCommitHistory(commitHistory)\n        } catch {\n            await setCommitHistory([])\n        }\n    }\n\n    @MainActor\n    private func setCommitHistory(_ history: [GitCommit]) {\n        self.commitHistory = history\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/InspectorArea/HistoryInspector/HistoryInspectorView.swift",
    "content": "//\n//  HistoryInspectorView.swift\n//  CodeEdit\n//\n//  Created by Nanashi Li on 2022/03/24.\n//\nimport SwiftUI\n\nstruct HistoryInspectorView: View {\n    @AppSettings(\\.sourceControl.git.showMergeCommitsPerFileLog)\n    var showMergeCommitsPerFileLog\n\n    @EnvironmentObject private var workspace: WorkspaceDocument\n\n    @EnvironmentObject private var editorManager: EditorManager\n\n    @ObservedObject private var model: HistoryInspectorModel\n\n    @State var selection: GitCommit?\n\n    /// Initialize with GitClient\n    /// - Parameter gitClient: a GitClient\n    init() {\n        self.model = .init()\n    }\n\n    var body: some View {\n        Group {\n            if model.sourceControlManager != nil {\n                VStack {\n                    if model.commitHistory.isEmpty {\n                        CEContentUnavailableView(\"No History\")\n                    } else {\n                        List(selection: $selection) {\n                            ForEach(model.commitHistory) { commit in\n                                HistoryInspectorItemView(commit: commit, selection: $selection)\n                                    .tag(commit)\n                                    .listRowSeparator(.hidden)\n                            }\n                        }\n                    }\n                }\n            } else {\n                NoSelectionInspectorView()\n            }\n        }\n        .onReceive(editorManager.activeEditor.objectWillChange) { _ in\n            Task {\n                await model.setFile(url: editorManager.activeEditor.selectedTab?.file.url.path())\n            }\n        }\n        .onChange(of: editorManager.activeEditor) { _, _ in\n            Task {\n                await model.setFile(url: editorManager.activeEditor.selectedTab?.file.url.path())\n            }\n        }\n        .onChange(of: editorManager.activeEditor.selectedTab) { _, _ in\n            Task {\n                await model.setFile(url: editorManager.activeEditor.selectedTab?.file.url.path())\n            }\n        }\n        .task {\n            await model.setWorkspace(sourceControlManager: workspace.sourceControlManager)\n            await model.setFile(url: editorManager.activeEditor.selectedTab?.file.url.path)\n        }\n        .onChange(of: showMergeCommitsPerFileLog) { _, _ in\n            Task {\n                await model.updateCommitHistory()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/InspectorArea/HistoryInspector/HistoryPopoverView.swift",
    "content": "//\n//  HistoryPopoverView.swift\n//  CodeEdit\n//\n//  Created by Nanashi Li on 2022/04/18.\n//\n\nimport SwiftUI\n\nstruct HistoryPopoverView: View {\n\n    private var commit: GitCommit\n\n    init(commit: GitCommit) {\n        self.commit = commit\n    }\n\n    var body: some View {\n        VStack {\n            CommitDetailsHeaderView(commit: commit)\n\n            Divider()\n\n            VStack(alignment: .leading, spacing: 0) {\n                // TODO: Implementation Needed\n                ActionButton(\"Show Commit\", systemImage: \"clock\") {}\n                    .disabled(true)\n                // TODO: Implementation Needed\n                ActionButton(\"Open in Code Review\", systemImage: \"arrow.left.arrow.right\") {}\n                    .disabled(true)\n                ActionButton(\"Email \\(commit.author)\", systemImage: \"envelope\") {\n                    let service = NSSharingService(named: NSSharingService.Name.composeEmail)\n                    service?.recipients = [commit.authorEmail]\n                    service?.perform(withItems: [])\n                }\n            }\n            .padding(.horizontal, 6)\n        }\n        .padding(.top)\n        .padding(.bottom, 5)\n        .frame(width: 310)\n    }\n\n    private struct ActionButton: View {\n\n        private var title: String\n        private var image: String\n        private var action: () -> Void\n\n        @State private var isHovering: Bool = false\n\n        @Environment(\\.isEnabled)\n        private var isEnabled\n\n        init(_ title: String, systemImage: String, action: @escaping () -> Void) {\n            self.title = title\n            self.image = systemImage\n            self.action = action\n        }\n\n        var body: some View {\n            Button {\n                action()\n            } label: {\n                Label(title: {\n                    Text(title)\n                        .lineLimit(1)\n                        .truncationMode(.middle)\n                }, icon: {\n                    Image(systemName: image)\n                        .frame(width: 16, alignment: .center)\n                        .padding(.leading, -2.5)\n                        .padding(.trailing, 2.5)\n                })\n                .frame(maxWidth: .infinity, alignment: .leading)\n                .foregroundColor(isHovering && isEnabled ? .white : .primary)\n                .contentShape(Rectangle())\n            }\n            .buttonStyle(.plain)\n            .padding(.horizontal, 10)\n            .padding(.vertical, 3)\n            .background(\n                EffectView.selectionBackground(isHovering && isEnabled)\n            )\n            .clipShape(RoundedRectangle(cornerRadius: 4))\n            .onHover { hovering in\n                isHovering = hovering\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/InspectorArea/InternalDevelopmentInspector/InternalDevelopmentInspectorView.swift",
    "content": "//\n//  InternalDevelopmentInspectorView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 2/19/24.\n//\n\nimport SwiftUI\n\nstruct InternalDevelopmentInspectorView: View {\n    var body: some View {\n        Form {\n            InternalDevelopmentNotificationsView()\n            InternalDevelopmentOutputView()\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/InspectorArea/InternalDevelopmentInspector/InternalDevelopmentNotificationsView.swift",
    "content": "//\n//  InternalDevelopmentNotificationsView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 2/19/24.\n//\n\nimport SwiftUI\n\nstruct InternalDevelopmentNotificationsView: View {\n    enum IconType: String, CaseIterable {\n        case symbol = \"Symbol\"\n        case image = \"Image\"\n        case text = \"Text\"\n        case emoji = \"Emoji\"\n    }\n\n    @State private var delay: Bool = false\n    @State private var sticky: Bool = false\n    @State private var selectedIconType: IconType = .symbol\n    @State private var actionButtonText: String = \"View\"\n    @State private var notificationTitle: String = \"Test Notification\"\n    @State private var notificationDescription: String = \"This is a test notification.\"\n\n    // Icon selection states\n    @State private var selectedSymbol: String?\n    @State private var selectedEmoji: String?\n    @State private var selectedText: String?\n    @State private var selectedImage: String?\n    @State private var selectedColor: Color?\n\n    private let availableSymbols = [\n        \"bell.fill\", \"bell.badge.fill\", \"exclamationmark.triangle.fill\",\n        \"info.circle.fill\", \"checkmark.seal.fill\", \"xmark.octagon.fill\",\n        \"bubble.left.fill\", \"envelope.fill\", \"phone.fill\", \"megaphone.fill\",\n        \"clock.fill\", \"calendar\", \"flag.fill\", \"bookmark.fill\", \"bolt.fill\",\n        \"shield.lefthalf.fill\", \"gift.fill\", \"heart.fill\", \"star.fill\",\n        \"curlybraces\"\n    ]\n\n    private let availableEmojis = [\n        \"🔔\", \"🚨\", \"⚠️\", \"👋\", \"😍\", \"😎\", \"😘\", \"😜\", \"😝\", \"😀\", \"😁\",\n        \"😂\", \"🤣\", \"😃\", \"😄\", \"😅\", \"😆\", \"😇\", \"😉\", \"😊\", \"😋\", \"😌\"\n    ]\n\n    private let availableImages = [\n        \"GitHubIcon\", \"BitBucketIcon\", \"GitLabIcon\"\n    ]\n\n    private let availableColors: [(String, Color)] = [\n        (\"Red\", .red), (\"Orange\", .orange), (\"Yellow\", .yellow),\n        (\"Green\", .green), (\"Mint\", .mint), (\"Cyan\", .cyan),\n        (\"Teal\", .teal), (\"Blue\", .blue), (\"Indigo\", .indigo),\n        (\"Purple\", .purple), (\"Pink\", .pink), (\"Gray\", .gray)\n    ]\n\n    var body: some View {\n        Section(\"Notifications\") {\n            Toggle(\"Delay 5s\", isOn: $delay)\n            Toggle(\"Sticky\", isOn: $sticky)\n\n            Picker(\"Icon Type\", selection: $selectedIconType) {\n                ForEach(IconType.allCases, id: \\.self) { type in\n                    Text(type.rawValue).tag(type)\n                }\n            }\n\n            Group {\n                switch selectedIconType {\n                case .symbol:\n                    Picker(\"Symbol\", selection: $selectedSymbol) {\n                        Label(\"Random\", systemImage: \"dice\").tag(nil as String?)\n                        Divider()\n                        ForEach(availableSymbols, id: \\.self) { symbol in\n                            Label(symbol, systemImage: symbol).tag(symbol as String?)\n                        }\n                    }\n                case .emoji:\n                    Picker(\"Emoji\", selection: $selectedEmoji) {\n                        Label(\"Random\", systemImage: \"dice\").tag(nil as String?)\n                        Divider()\n                        ForEach(availableEmojis, id: \\.self) { emoji in\n                            Text(emoji).tag(emoji as String?)\n                        }\n                    }\n                case .text:\n                    Picker(\"Text\", selection: $selectedText) {\n                        Label(\"Random\", systemImage: \"dice\").tag(nil as String?)\n                        Divider()\n                        ForEach(\"ABCDEFGHIJKLMNOPQRSTUVWXYZ\".map { String($0) }, id: \\.self) { letter in\n                            Text(letter).tag(letter as String?)\n                        }\n                    }\n                case .image:\n                    Picker(\"Image\", selection: $selectedImage) {\n                        Label(\"Random\", systemImage: \"dice\").tag(nil as String?)\n                        Divider()\n                        ForEach(availableImages, id: \\.self) { image in\n                            Text(image).tag(image as String?)\n                        }\n                    }\n                }\n\n                if selectedIconType == .symbol || selectedIconType == .text || selectedIconType == .emoji {\n                    Picker(\"Icon Color\", selection: $selectedColor) {\n                        Label(\"Random\", systemImage: \"dice\").tag(nil as Color?)\n                        Divider()\n                        ForEach(availableColors, id: \\.0) { name, color in\n                            HStack {\n                                Circle()\n                                    .fill(color)\n                                    .frame(width: 12, height: 12)\n                                Text(name)\n                            }.tag(color as Color?)\n                        }\n                    }\n                }\n            }\n\n            TextField(\"Title\", text: $notificationTitle)\n            TextField(\"Description\", text: $notificationDescription, axis: .vertical)\n                .lineLimit(1...5)\n            TextField(\"Action Button\", text: $actionButtonText)\n\n            Button(\"Add Notification\") {\n                let action = {\n                    switch selectedIconType {\n                    case .symbol:\n                        let iconSymbol = selectedSymbol ?? availableSymbols.randomElement() ?? \"bell.fill\"\n                        let iconColor = selectedColor ?? availableColors.randomElement()?.1 ?? .blue\n\n                        NotificationManager.shared.post(\n                            iconSymbol: iconSymbol,\n                            iconColor: iconColor,\n                            title: notificationTitle,\n                            description: notificationDescription,\n                            actionButtonTitle: actionButtonText,\n                            action: {\n                                print(\"Test notification action triggered\")\n                            },\n                            isSticky: sticky\n                        )\n                    case .image:\n                        let imageName = selectedImage ?? availableImages.randomElement() ?? \"GitHubIcon\"\n\n                        NotificationManager.shared.post(\n                            iconImage: Image(imageName),\n                            title: notificationTitle,\n                            description: notificationDescription,\n                            actionButtonTitle: actionButtonText,\n                            action: {\n                                print(\"Test notification action triggered\")\n                            },\n                            isSticky: sticky\n                        )\n                    case .text:\n                        let text = selectedText ?? randomLetter()\n                        let iconColor = selectedColor ?? availableColors.randomElement()?.1 ?? .blue\n\n                        NotificationManager.shared.post(\n                            iconText: text,\n                            iconTextColor: .white,\n                            iconColor: iconColor,\n                            title: notificationTitle,\n                            description: notificationDescription,\n                            actionButtonTitle: actionButtonText,\n                            action: {\n                                print(\"Test notification action triggered\")\n                            },\n                            isSticky: sticky\n                        )\n                    case .emoji:\n                        let emoji = selectedEmoji ?? availableEmojis.randomElement() ?? \"🔔\"\n                        let iconColor = selectedColor ?? availableColors.randomElement()?.1 ?? .blue\n\n                        NotificationManager.shared.post(\n                            iconText: emoji,\n                            iconTextColor: .white,\n                            iconColor: iconColor,\n                            title: notificationTitle,\n                            description: notificationDescription,\n                            actionButtonTitle: actionButtonText,\n                            action: {\n                                print(\"Test notification action triggered\")\n                            },\n                            isSticky: sticky\n                        )\n                    }\n                }\n\n                if delay {\n                    DispatchQueue.main.asyncAfter(deadline: .now() + 5) {\n                        action()\n                    }\n                } else {\n                    action()\n                }\n            }\n        }\n    }\n\n    private func randomLetter() -> String {\n        let letters = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\".map { String($0) }\n        return letters.randomElement() ?? \"A\"\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/InspectorArea/InternalDevelopmentInspector/InternalDevelopmentOutputView.swift",
    "content": "//\n//  InternalDevelopmentOutputView.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/18/25.\n//\n\nimport SwiftUI\n\nstruct InternalDevelopmentOutputView: View {\n    var body: some View {\n        Section(\"Output Utility\") {\n            Button(\"Error Log\") {\n                pushLog(.error)\n            }\n            Button(\"Warning Log\") {\n                pushLog(.warning)\n            }\n            Button(\"Info Log\") {\n                pushLog(.info)\n            }\n            Button(\"Debug Log\") {\n                pushLog(.debug)\n            }\n        }\n\n    }\n\n    func pushLog(_ level: UtilityAreaLogLevel) {\n        InternalDevelopmentOutputSource.shared.pushLog(\n            .init(\n                message: randomString(),\n                subsystem: \"internal.development\",\n                category: \"Logs\",\n                level: level\n            )\n        )\n    }\n\n    func randomString() -> String {\n        let strings = (\"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce molestie, dui et consectetur\"\n        + \"porttitor, orci lectus fermentum augue, eu faucibus lectus nisl id velit. Suspendisse in mi nunc. Aliquam\"\n        + \"non dolor eu eros mollis euismod. Praesent mollis mauris at ex dapibus ornare. Ut imperdiet\"\n        + \"finibus lacus ut aliquam. Vivamus semper, mauris in condimentum volutpat, quam erat eleifend ligula,\"\n        + \"nec tincidunt sem ante et ex. Sed dui magna, placerat quis orci at, bibendum molestie massa. Maecenas\"\n        + \"velit nunc, vehicula eu venenatis vel, tincidunt id purus. Morbi eu dignissim arcu, sed ornare odio.\"\n        + \"Nam vestibulum tempus nibh id finibus.\").split(separator: \" \")\n        let count = Int.random(in: 0..<25)\n        return (0..<count).compactMap { _ in\n            strings.randomElement()\n        }\n        .joined(separator: \" \")\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/InspectorArea/Models/InspectorTab.swift",
    "content": "//\n//  InspectorTab.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 02/06/2023.\n//\n\nimport SwiftUI\nimport CodeEditKit\nimport ExtensionFoundation\n\nenum InspectorTab: WorkspacePanelTab {\n    case file\n    case gitHistory\n    case internalDevelopment\n    case uiExtension(endpoint: AppExtensionIdentity, data: ResolvedSidebar.SidebarStore)\n\n    var systemImage: String {\n        switch self {\n        case .file:\n            return \"doc\"\n        case .gitHistory:\n            return \"clock\"\n        case .internalDevelopment:\n            return \"hammer\"\n        case .uiExtension(_, let data):\n            return data.icon ?? \"e.square\"\n        }\n    }\n\n    var id: String {\n        if case .uiExtension(let endpoint, let data) = self {\n            return endpoint.bundleIdentifier + data.sceneID\n        }\n        return title\n    }\n\n    var title: String {\n        switch self {\n        case .file:\n            return \"File Inspector\"\n        case .gitHistory:\n            return \"History Inspector\"\n        case .internalDevelopment:\n            return \"Internal Development\"\n        case .uiExtension(_, let data):\n            return data.help ?? data.sceneID\n        }\n    }\n\n    var body: some View {\n        switch self {\n        case .file:\n            FileInspectorView()\n        case .gitHistory:\n            HistoryInspectorView()\n        case .internalDevelopment:\n            InternalDevelopmentInspectorView()\n        case let .uiExtension(endpoint, data):\n            ExtensionSceneView(with: endpoint, sceneID: data.sceneID)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/InspectorArea/ViewModels/InspectorAreaViewModel.swift",
    "content": "//\n//  InspectorAreaViewModel.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 9/23/23.\n//\n\nimport Foundation\n\nclass InspectorAreaViewModel: ObservableObject {\n    @Published var selectedTab: InspectorTab? = .file\n    /// The tab bar items in the Inspector\n    @Published var tabItems: [InspectorTab] = []\n\n    func setInspectorTab(tab newTab: InspectorTab) {\n        selectedTab = newTab\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/InspectorArea/Views/InspectorAreaView.swift",
    "content": "//\n//  InspectorAreaView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 3/21/22.\n//\n\nimport SwiftUI\n\nstruct InspectorAreaView: View {\n    @EnvironmentObject private var workspace: WorkspaceDocument\n    @EnvironmentObject private var editorManager: EditorManager\n    @ObservedObject private var extensionManager = ExtensionManager.shared\n    @ObservedObject public var viewModel: InspectorAreaViewModel\n\n    @AppSettings(\\.general.inspectorTabBarPosition)\n    var sidebarPosition: SettingsData.SidebarTabBarPosition\n\n    @AppSettings(\\.developerSettings.showInternalDevelopmentInspector)\n    var showInternalDevelopmentInspector\n\n    init(viewModel: InspectorAreaViewModel) {\n        self.viewModel = viewModel\n        updateTabs()\n    }\n\n    private func updateTabs() {\n        var tabs: [InspectorTab] = [.file, .gitHistory]\n\n        if showInternalDevelopmentInspector {\n            tabs.append(.internalDevelopment)\n        }\n\n        viewModel.tabItems = tabs + extensionManager\n            .extensions\n            .map { ext in\n                ext.availableFeatures.compactMap {\n                    if case .sidebarItem(let data) = $0, data.kind == .inspector {\n                        return InspectorTab.uiExtension(endpoint: ext.endpoint, data: data)\n                    }\n                    return nil\n                }\n            }\n            .joined()\n    }\n\n    var body: some View {\n        WorkspacePanelView(\n            viewModel: viewModel,\n            selectedTab: $viewModel.selectedTab,\n            tabItems: $viewModel.tabItems,\n            sidebarPosition: sidebarPosition\n        )\n        .formStyle(.grouped)\n        .accessibilityElement(children: .contain)\n        .accessibilityLabel(\"inspector\")\n        .onChange(of: showInternalDevelopmentInspector) { _, _ in\n            updateTabs()\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/InspectorArea/Views/InspectorField.swift",
    "content": "//\n//  InspectorField.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 1/12/23.\n//\n\nimport SwiftUI\n\nstruct InspectorField<Content: View>: View {\n    var label: String\n    let content: Content\n\n    init(_ label: String, @ViewBuilder _ content: () -> Content) {\n        self.label = label\n        self.content = content()\n    }\n\n    var body: some View {\n        HStack(alignment: .top, spacing: 5) {\n            Text(label)\n                .foregroundColor(.primary)\n                .fontWeight(.regular)\n                .font(.system(size: 10))\n                .padding(.top, 3)\n                .frame(maxWidth: 72, alignment: .trailing)\n            VStack(alignment: .leading) {\n                content\n            }\n            .frame(maxWidth: .infinity)\n        }\n    }\n}\n\nstruct InspectorField_Previews: PreviewProvider {\n    static var previews: some View {\n        InspectorField(\"Section Label\") {\n            Text(\"Preview\")\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/InspectorArea/Views/InspectorSection.swift",
    "content": "//\n//  InspectorSection.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 1/12/23.\n//\n\nimport SwiftUI\n\nstruct InspectorSection<Content: View>: View {\n    var label: String\n    let content: Content\n\n    init(_ label: String, @ViewBuilder _ content: () -> Content) {\n        self.label = label\n        self.content = content()\n    }\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 11) {\n            Text(label)\n                .foregroundColor(.secondary)\n                .fontWeight(.bold)\n                .font(.system(size: 12))\n                .padding(.leading, -1)\n            VStack(alignment: .trailing, spacing: 5) {\n                content\n                Divider()\n            }\n        }\n    }\n}\n\nstruct InspectorSection_Previews: PreviewProvider {\n    static var previews: some View {\n        InspectorSection(\"Section Label\") {\n            Text(\"Preview\")\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/InspectorArea/Views/NoSelectionInspectorView.swift",
    "content": "//\n//  NoSelectionView.swift\n//  CodeEdit\n//\n//  Created by Nanashi Li on 2022/04/18.\n//\n\nimport SwiftUI\n\nstruct NoSelectionInspectorView: View {\n    var body: some View {\n        CEContentUnavailableView(\"No Selection\")\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Keybindings/CommandManager.swift",
    "content": "//\n//  CommandManager.swift\n//\n//  Created by Alex on 23.05.2022.\n//\n\nimport Foundation\n\n/**\nThe object of this class intended to be a hearth of command palette. This object only exists as singleton.\n In Order to access its instance use `CommandManager.shared`\n\n```\n /* To add or execute command see snipper below */\nlet mgr = CommandManager.shared\nlet wrap = CommandClosureWrapper.init(closure: {\n    print(\"testing closure\")\n})\n\nmgr.addCommand(name: \"test\", command: wrap)\nmgr.executeCommand(\"test\")\n ```\n */\n\nfinal class CommandManager: ObservableObject {\n    @Published private var commandsList: [String: Command]\n\n    private init() {\n        commandsList = [:]\n    }\n\n    static let shared: CommandManager = .init()\n\n    func addCommand(name: String, title: String, id: String, command: @escaping () -> Void) {\n        let command = Command.init(id: name, title: title, closureWrapper: command)\n        commandsList[id] = command\n    }\n\n    var commands: [Command] {\n        return commandsList.map { $0.value }\n    }\n\n    func executeCommand(_ id: String) {\n        commandsList[id]?.closureWrapper()\n    }\n}\n\n/// Command struct uses as a wrapper for command. Used by command palette to call selected commands.\nstruct Command: Identifiable, Hashable {\n\n    static func == (lhs: Command, rhs: Command) -> Bool {\n        return lhs.id == rhs.id\n    }\n\n    static func < (lhs: Command, rhs: Command) -> Bool {\n        return false\n    }\n\n    func hash(into hasher: inout Hasher) {\n        hasher.combine(id)\n    }\n\n    let id: String\n    let title: String\n    let closureWrapper: () -> Void\n}\n"
  },
  {
    "path": "CodeEdit/Features/Keybindings/KeybindingManager.swift",
    "content": "//\n//  KeybindingManager.swift\n//\n//  Created by Alex on 09.05.2022.\n//\n\nimport Foundation\nimport SwiftUI\n\nfinal class KeybindingManager {\n    /// Array which contains all available keyboard shortcuts\n    var keyboardShortcuts = [String: KeyboardShortcutWrapper]()\n\n    private init() {\n        loadKeybindings()\n    }\n\n    /// Static method to access singleton\n    static let shared: KeybindingManager = .init()\n\n    // We need this fallback shortcut because optional shortcuts available only from 12.3, while we have target of 12.0x\n    var fallbackShortcut = KeyboardShortcutWrapper(\n        name: \"?\",\n        description: \"Test\",\n        context: \"Fallback\",\n        keybinding: \"?\",\n        modifier: \"shift\",\n        id: \"fallback\"\n    )\n\n    /// Adds new shortcut\n    func addNewShortcut(shortcut: KeyboardShortcutWrapper, name: String) {\n        keyboardShortcuts[name] = shortcut\n    }\n\n    private func loadKeybindings() {\n\n        let bindingsURL = Bundle.main.url(forResource: \"default_keybindings.json\", withExtension: nil)\n        if let json = try? Data(contentsOf: bindingsURL!) {\n            do {\n                let prefs = try JSONDecoder().decode([KeyboardShortcutWrapper].self, from: json)\n                for pref in prefs {\n                    addNewShortcut(shortcut: pref, name: pref.id)\n                }\n                } catch {\n                    print(\"error:\\(error)\")\n                }\n        }\n        return\n    }\n\n    /// Get shortcut by name\n    /// - Parameter name: shortcut name\n    /// - Returns: KeyboardShortcutWrapper\n    func named(with name: String) -> KeyboardShortcutWrapper {\n        let foundElement = keyboardShortcuts[name]\n        return foundElement != nil ? foundElement! : fallbackShortcut\n    }\n\n}\n\n/// Wrapper for KeyboardShortcut. It contains name, keybindings.\nstruct KeyboardShortcutWrapper: Codable, Hashable {\n    var keyboardShortcut: KeyboardShortcut {\n        return KeyboardShortcut.init(.init(Character(keybinding)), modifiers: parsedModifier)\n    }\n\n    var parsedModifier: EventModifiers {\n        switch modifier {\n        case \"command\":\n            return EventModifiers.command\n        case \"shift\":\n            return EventModifiers.shift\n        case \"option\":\n            return EventModifiers.option\n        case \"control\":\n            return EventModifiers.control\n        default:\n            return EventModifiers.command\n        }\n    }\n    var name: String\n    var description: String\n    var context: String\n    var keybinding: String\n    var modifier: String\n    var id: String\n\n    enum CodingKeys: String, CodingKey {\n        case name\n        case description\n        case context\n        case keybinding\n        case modifier\n        case id\n    }\n\n    init(name: String, description: String, context: String, keybinding: String, modifier: String, id: String) {\n        self.name = name\n        self.description = description\n        self.context = context\n        self.keybinding = keybinding\n        self.modifier = modifier\n        self.id = id\n    }\n\n    init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        name = try container.decode(String.self, forKey: .name)\n        description = try container.decode(String.self, forKey: .description)\n        context = try container.decode(String.self, forKey: .context)\n        keybinding = try container.decode(String.self, forKey: .keybinding)\n        modifier = try container.decode(String.self, forKey: .modifier)\n        id = try container.decode(String.self, forKey: .id)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Keybindings/ModifierKeysObserver.swift",
    "content": "//\n//  ModifierKeysObserver.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 04/03/2023.\n//\n\nimport SwiftUI\nimport Combine\n\nstruct EventModifierEnvironmentKey: EnvironmentKey {\n    static var defaultValue: NSEvent.ModifierFlags = []\n}\n\nextension EnvironmentValues {\n    var modifierKeys: EventModifierEnvironmentKey.Value {\n        get { self[EventModifierEnvironmentKey.self] }\n        set { self[EventModifierEnvironmentKey.self] = newValue }\n    }\n}\n\nextension NSEvent {\n    static func publisher(scope: Publisher.Scope, matching: EventTypeMask) -> Publisher {\n        return Publisher(scope: scope, matching: matching)\n    }\n\n    public struct Publisher: Combine.Publisher {\n        public enum Scope {\n            case local, global\n        }\n\n        public typealias Output = NSEvent\n\n        public typealias Failure = Never\n\n        let scope: Scope\n        let matching: EventTypeMask\n\n        public func receive<S>(subscriber: S) where S: Subscriber, Self.Failure == S.Failure, Self.Output == S.Input {\n            let subscription = Subscription(scope: scope, matching: matching, subscriber: subscriber)\n            subscriber.receive(subscription: subscription)\n        }\n    }\n}\n\nprivate extension NSEvent.Publisher {\n    final class Subscription<S: Subscriber> where S.Input == NSEvent, S.Failure == Never {\n        fileprivate let lock = NSLock()\n        fileprivate var demand = Subscribers.Demand.none\n        private var monitor: Any?\n\n        fileprivate let subscriberLock = NSRecursiveLock()\n\n        init(scope: Scope, matching: NSEvent.EventTypeMask, subscriber: S) {\n            switch scope {\n            case .local:\n                self.monitor = NSEvent.addLocalMonitorForEvents(matching: matching) { [weak self] (event) -> NSEvent? in\n                    self?.didReceive(event: event, subscriber: subscriber)\n                    return event\n                }\n\n            case .global:\n                self.monitor = NSEvent.addGlobalMonitorForEvents(matching: matching) { [weak self] in\n                    self?.didReceive(event: $0, subscriber: subscriber)\n                }\n            }\n\n        }\n\n        deinit {\n            if let monitor {\n                NSEvent.removeMonitor(monitor)\n            }\n        }\n\n        func didReceive(event: NSEvent, subscriber: S) {\n            let val = { () -> Subscribers.Demand in\n                lock.lock()\n                defer { lock.unlock() }\n                let before = demand\n                if demand > 0 {\n                    demand -= 1\n                }\n                return before\n            }()\n\n            guard val > 0 else { return }\n\n            let newDemand = subscriber.receive(event)\n\n            lock.lock()\n            demand += newDemand\n            lock.unlock()\n        }\n    }\n}\n\nextension NSEvent.Publisher.Subscription: Combine.Subscription {\n    func request(_ demand: Subscribers.Demand) {\n        lock.lock()\n        defer { lock.unlock() }\n        self.demand += demand\n    }\n\n    func cancel() {\n        lock.lock()\n        defer { lock.unlock() }\n        guard let monitor else { return }\n\n        self.monitor = nil\n        NSEvent.removeMonitor(monitor)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Keybindings/default_keybindings.json",
    "content": "[\n    {\n        \"id\": \"copy\", \"name\": \"copy\", \"description\": \"Copy command\", \"context\": \"Global\", \"modifier\": \"command\", \"keybinding\": \"c\"\n    },\n    {\n        \"id\": \"paste\", \"name\": \"paste\", \"description\": \"Copy command\", \"context\": \"Global\", \"modifier\": \"command\", \"keybinding\": \"c\"\n    }\n\n]\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Features/DocumentSync/LSPContentCoordinator.swift",
    "content": "//\n//  LSPContentCoordinator.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 9/12/24.\n//\n\nimport AppKit\nimport AsyncAlgorithms\nimport CodeEditSourceEditor\nimport CodeEditTextView\nimport LanguageServerProtocol\n\n/// This content coordinator forwards content notifications from the editor's text storage to a language service.\n///\n/// This is a text view coordinator so that it can be installed on an open editor. It is kept as a property on\n/// ``CodeFileDocument`` since the language server does all it's document management using instances of that type.\n///\n/// Language servers expect edits to be sent in chunks (and it helps reduce processing overhead). To do this, this class\n/// keeps an async stream around for the duration of its lifetime. The stream is sent edit notifications, which are then\n/// chunked into 250ms timed groups before being sent to the ``LanguageServer``.\nclass LSPContentCoordinator<DocumentType: LanguageServerDocument>: TextViewCoordinator, TextViewDelegate {\n    // Required to avoid a large_tuple lint error\n    private struct SequenceElement: Sendable {\n        let uri: String\n        let range: LSPRange\n        let string: String\n    }\n\n    private var editedRange: LSPRange?\n    private var sequenceContinuation: AsyncStream<SequenceElement>.Continuation?\n    private var task: Task<Void, Never>?\n\n    weak var languageServer: LanguageServer<DocumentType>?\n    var documentURI: String?\n\n    /// Initializes a content coordinator, and begins an async stream of updates\n    init(documentURI: String? = nil, languageServer: LanguageServer<DocumentType>? = nil) {\n        self.documentURI = documentURI\n        self.languageServer = languageServer\n\n        setUpUpdatesTask()\n    }\n\n    func setUp(server: LanguageServer<DocumentType>, document: DocumentType) {\n        languageServer = server\n        documentURI = document.languageServerURI\n    }\n\n    func setUpUpdatesTask() {\n        task?.cancel()\n        // Create this stream here so it's always set up when the text view is set up, rather than only once on init.\n        let stream = AsyncStream { continuation in\n            self.sequenceContinuation = continuation\n        }\n\n        task = Task.detached { [weak self] in\n            // Send edit events every 250ms\n            for await events in stream.chunked(by: .repeating(every: .milliseconds(250), clock: .continuous)) {\n                guard !Task.isCancelled, self != nil else { return }\n                guard !events.isEmpty, let uri = events.first?.uri else { continue }\n                // Errors thrown here are already logged, not much else to do right now.\n                try? await self?.languageServer?.documentChanged(\n                    uri: uri,\n                    changes: events.map {\n                        LanguageServer.DocumentChange(replacingContentsIn: $0.range, with: $0.string)\n                    }\n                )\n            }\n        }\n    }\n\n    func prepareCoordinator(controller: TextViewController) {\n        setUpUpdatesTask()\n    }\n\n    /// We grab the lsp range before the content (and layout) is changed so we get correct line/col info for the\n    /// language server range.\n    func textView(_ textView: TextView, willReplaceContentsIn range: NSRange, with string: String) {\n        self.editedRange = textView.lspRangeFrom(nsRange: range)\n    }\n\n    func textView(_ textView: TextView, didReplaceContentsIn range: NSRange, with string: String) {\n        guard let lspRange = editedRange, let documentURI else {\n            return\n        }\n        self.editedRange = nil\n        self.sequenceContinuation?.yield(SequenceElement(uri: documentURI, range: lspRange, string: string))\n    }\n\n    func destroy() {\n        task?.cancel()\n        task = nil\n        sequenceContinuation?.finish()\n        sequenceContinuation = nil\n    }\n\n    deinit {\n        destroy()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenHighlightProvider.swift",
    "content": "//\n//  SemanticTokenHighlightProvider.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 12/26/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\nimport CodeEditSourceEditor\nimport CodeEditTextView\nimport CodeEditLanguages\n\n/// Provides semantic token information from a language server for a source editor view.\n///\n/// This class works in tangent with the ``LanguageServer`` class to ensure we don't unnecessarily request new tokens\n/// if the document isn't updated. The ``LanguageServer`` will call the\n/// ``SemanticTokenHighlightProvider/documentDidChange`` method, which in turn refreshes the semantic token storage.\n///\n/// That behavior may not be intuitive due to the\n/// ``SemanticTokenHighlightProvider/applyEdit(textView:range:delta:completion:)`` method. One might expect this class\n/// to respond to that method immediately, but it does not. It instead stores the completion passed in that method until\n/// it can respond to the edit with invalidated indices.\nfinal class SemanticTokenHighlightProvider<\n    Storage: GenericSemanticTokenStorage,\n    DocumentType: LanguageServerDocument\n>: HighlightProviding {\n    enum HighlightError: Error {\n        case lspRangeFailure\n    }\n\n    typealias EditCallback = @MainActor (Result<IndexSet, any Error>) -> Void\n    typealias HighlightCallback = @MainActor (Result<[HighlightRange], any Error>) -> Void\n\n    private var tokenMap: SemanticTokenMap?\n    private var documentURI: String?\n    weak var languageServer: LanguageServer<DocumentType>?\n    private weak var textView: TextView?\n\n    private var lastEditCallback: EditCallback?\n    private var pendingHighlightCallbacks: [HighlightCallback] = []\n    private var storage: Storage\n\n    var documentRange: NSRange {\n        textView?.documentRange ?? .zero\n    }\n\n    init(\n        tokenMap: SemanticTokenMap? = nil,\n        languageServer: LanguageServer<DocumentType>? = nil,\n        documentURI: String? = nil\n    ) {\n        self.tokenMap = tokenMap\n        self.languageServer = languageServer\n        self.documentURI = documentURI\n        self.storage = Storage()\n    }\n\n    func setUp(server: LanguageServer<DocumentType>, document: DocumentType) {\n        languageServer = server\n        documentURI = document.languageServerURI\n        tokenMap = server.highlightMap\n    }\n\n    // MARK: - Language Server Content Lifecycle\n\n    /// Called when the language server finishes sending a document update.\n    ///\n    /// This method first checks if this object has any semantic tokens. If not, requests new tokens and responds to the\n    /// `pendingHighlightCallbacks` queue with cancellation errors, causing the highlighter to re-query those indices.\n    ///\n    /// If this object already has some tokens, it determines whether or not we can request a token delta and\n    /// performs the request.\n    func documentDidChange() async throws {\n        guard let languageServer, let textView else {\n            return\n        }\n\n        guard storage.hasReceivedData else {\n            // We have no semantic token info, request it!\n            try await requestTokens(languageServer: languageServer, textView: textView)\n            await MainActor.run {\n                for callback in pendingHighlightCallbacks {\n                    callback(.failure(HighlightProvidingError.operationCancelled))\n                }\n                pendingHighlightCallbacks.removeAll()\n            }\n            return\n        }\n\n        // The document was updated. Update our token cache and send the invalidated ranges for the editor to handle.\n        if let lastResultId = storage.lastResultId {\n            try await requestDeltaTokens(languageServer: languageServer, textView: textView, lastResultId: lastResultId)\n            return\n        }\n\n        try await requestTokens(languageServer: languageServer, textView: textView)\n    }\n\n    // MARK: - LSP Token Requests\n\n    /// Requests and applies a token delta. Requires a previous response identifier.\n    private func requestDeltaTokens(\n        languageServer: LanguageServer<DocumentType>,\n        textView: TextView,\n        lastResultId: String\n    ) async throws {\n        guard let documentURI,\n              let response = try await languageServer.requestSemanticTokens(\n            for: documentURI,\n            previousResultId: lastResultId\n        ) else {\n            return\n        }\n        switch response {\n        case let .optionA(tokenData):\n            await applyEntireResponse(tokenData, callback: lastEditCallback)\n        case let .optionB(deltaData):\n            await applyDeltaResponse(deltaData, callback: lastEditCallback, textView: textView)\n        }\n    }\n\n    /// Requests and applies tokens for an entire document. This does not require a previous response id, and should be\n    /// used in place of `requestDeltaTokens` when that's the case.\n    private func requestTokens(languageServer: LanguageServer<DocumentType>, textView: TextView) async throws {\n        guard let documentURI, let response = try await languageServer.requestSemanticTokens(for: documentURI) else {\n            return\n        }\n        await applyEntireResponse(response, callback: lastEditCallback)\n    }\n\n    // MARK: - Apply LSP Response\n\n    /// Applies a delta response from the LSP to our storage.\n    private func applyDeltaResponse(_ data: SemanticTokensDelta, callback: EditCallback?, textView: TextView?) async {\n        let lspRanges = storage.applyDelta(data)\n        lastEditCallback = nil // Don't use this callback again.\n        await MainActor.run {\n            let ranges = lspRanges.compactMap { textView?.nsRangeFrom($0) }\n            callback?(.success(IndexSet(ranges: ranges)))\n        }\n    }\n\n    private func applyEntireResponse(_ data: SemanticTokens, callback: EditCallback?) async {\n        storage.setData(data)\n        lastEditCallback = nil // Don't use this callback again.\n        await callback?(.success(IndexSet(integersIn: documentRange)))\n    }\n\n    // MARK: - Highlight Provider Conformance\n\n    func setUp(textView: TextView, codeLanguage: CodeLanguage) {\n        // Send off a request to get the initial token data\n        self.textView = textView\n        Task {\n            try await self.documentDidChange()\n        }\n    }\n\n    func applyEdit(textView: TextView, range: NSRange, delta: Int, completion: @escaping EditCallback) {\n        if let lastEditCallback {\n            lastEditCallback(.success(IndexSet())) // Don't throw a cancellation error\n        }\n        lastEditCallback = completion\n    }\n\n    func queryHighlightsFor(textView: TextView, range: NSRange, completion: @escaping HighlightCallback) {\n        guard storage.hasReceivedData else {\n            pendingHighlightCallbacks.append(completion)\n            return\n        }\n\n        guard let lspRange = textView.lspRangeFrom(nsRange: range), let tokenMap else {\n            completion(.failure(HighlightError.lspRangeFailure))\n            return\n        }\n        let rawTokens = storage.getTokensFor(range: lspRange)\n        let highlights = tokenMap\n            .decode(tokens: rawTokens, using: textView)\n            .compactMap { highlightRange -> HighlightRange? in\n                // Filter out empty ranges\n                guard highlightRange.capture != nil || !highlightRange.modifiers.isEmpty,\n                      // Clamp the highlight range to the queried range.\n                      let intersection = highlightRange.range.intersection(range),\n                      intersection.isEmpty == false else {\n                    return nil\n                }\n                return HighlightRange(\n                    range: intersection,\n                    capture: highlightRange.capture,\n                    modifiers: highlightRange.modifiers\n                )\n            }\n        completion(.success(highlights))\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenMap.swift",
    "content": "//\n//  SemanticTokenMap.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 11/10/24.\n//\n\nimport LanguageClient\nimport LanguageServerProtocol\nimport CodeEditSourceEditor\nimport CodeEditTextView\n\n// swiftlint:disable line_length\n/// Creates a mapping from a language server's semantic token options to a format readable by CodeEdit.\n/// Provides a convenience method for mapping tokens received from the server to highlight ranges suitable for\n/// highlighting in the editor.\n///\n/// Use this type to handle the initially received semantic highlight capabilities structures. This type will figure\n/// out how to read it into a format it can use.\n///\n/// After initialization, the map is static until the server is reinitialized. Consequently, this type is `Sendable`\n/// and immutable after initialization.\n///\n/// This type is not coupled to any text system via the use of the ``SemanticTokenMapRangeProvider``. When decoding to\n/// highlight ranges, provide a type that can provide ranges for highlighting.\n///\n/// [LSP Spec](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#semanticTokensLegend)\nstruct SemanticTokenMap: Sendable { // swiftlint:enable line_length\n    private let tokenTypeMap: [CaptureName?]\n    private let modifierMap: [CaptureModifier?]\n\n    init(semanticCapability: TwoTypeOption<SemanticTokensOptions, SemanticTokensRegistrationOptions>) {\n        let legend: SemanticTokensLegend\n        switch semanticCapability {\n        case .optionA(let tokensOptions):\n            legend = tokensOptions.legend\n        case .optionB(let tokensRegistrationOptions):\n            legend = tokensRegistrationOptions.legend\n        }\n\n        tokenTypeMap = legend.tokenTypes.map { CaptureName.fromString($0) }\n        modifierMap = legend.tokenModifiers.map { CaptureModifier.fromString($0) }\n    }\n\n    /// Decodes the compressed semantic token data into a `HighlightRange` type for use in an editor.\n    /// This is marked main actor to prevent runtime errors, due to the use of the actor-isolated `rangeProvider`.\n    /// - Parameters:\n    ///   - tokens: Encoded semantic tokens type from a language server.\n    ///   - rangeProvider: The provider to use to translate token ranges to text view ranges.\n    /// - Returns: An array of decoded highlight ranges.\n    @MainActor\n    func decode(tokens: SemanticTokens, using rangeProvider: SemanticTokenMapRangeProvider) -> [HighlightRange] {\n        return decode(tokens: tokens.decode(), using: rangeProvider)\n    }\n\n    /// Decodes the compressed semantic token data into a `HighlightRange` type for use in an editor.\n    /// This is marked main actor to prevent runtime errors, due to the use of the actor-isolated `rangeProvider`.\n    /// - Parameters:\n    ///   - tokens: Decoded semantic tokens from a language server.\n    ///   - rangeProvider: The provider to use to translate token ranges to text view ranges.\n    /// - Returns: An array of decoded highlight ranges.\n    @MainActor\n    func decode(tokens: [SemanticToken], using rangeProvider: SemanticTokenMapRangeProvider) -> [HighlightRange] {\n        tokens.compactMap { token in\n            guard let range = rangeProvider.nsRangeFrom(line: token.line, char: token.char, length: token.length) else {\n                return nil\n            }\n\n            // Only modifiers are bit packed, capture types are given as a simple index into the ``tokenTypeMap``\n            let modifiers = decodeModifier(token.modifiers)\n\n            let type = Int(token.type)\n            let capture = tokenTypeMap.indices.contains(type) ? tokenTypeMap[type] : nil\n\n            return HighlightRange(\n                range: range,\n                capture: capture,\n                modifiers: modifiers\n            )\n        }\n    }\n\n    /// Decodes a raw modifier value into a set of capture modifiers.\n    /// - Parameter raw: The raw modifier integer to decode.\n    /// - Returns: A set of modifiers for highlighting.\n    func decodeModifier(_ raw: UInt32) -> CaptureModifierSet {\n        var modifiers: CaptureModifierSet = []\n        var raw = raw\n        while raw > 0 {\n            let idx = raw.trailingZeroBitCount\n            raw &= ~(1 << idx)\n            // We don't use `[safe:]` because it creates a double optional here. If someone knows how to extend\n            // a collection of optionals to make that return only a single optional this could be updated.\n            guard let modifier = modifierMap.indices.contains(idx) ? modifierMap[idx] : nil else {\n                continue\n            }\n            modifiers.insert(modifier)\n        }\n        return modifiers\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenMapRangeProvider.swift",
    "content": "//\n//  SemanticTokenMapRangeProvider.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 12/19/24.\n//\n\nimport Foundation\n\n@MainActor\nprotocol SemanticTokenMapRangeProvider {\n    func nsRangeFrom(line: UInt32, char: UInt32, length: UInt32) -> NSRange?\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenStorage/GenericSemanticTokenStorage.swift",
    "content": "//\n//  GenericSemanticTokenStorage.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 12/26/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\nimport CodeEditSourceEditor\n\n/// Defines a protocol for an object to provide storage for semantic tokens.\n/// \n/// There is only one concrete type that conforms to this in CE, but this protocol is useful in testing.\n/// See ``SemanticTokenStorage``.\nprotocol GenericSemanticTokenStorage: AnyObject {\n    var lastResultId: String? { get }\n    var hasReceivedData: Bool { get }\n\n    init()\n\n    func getTokensFor(range: LSPRange) -> [SemanticToken]\n    func setData(_ data: borrowing SemanticTokens)\n    func applyDelta(_ deltas: SemanticTokensDelta) -> [SemanticTokenRange]\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenStorage/SemanticTokenRange.swift",
    "content": "//\n//  SemanticTokenRange.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 12/26/24.\n//\n\n/// Represents the range of a semantic token.\nstruct SemanticTokenRange {\n    let line: UInt32\n    let char: UInt32\n    let length: UInt32\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenStorage/SemanticTokenStorage.swift",
    "content": "//\n//  SemanticTokenStorage.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 12/26/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\nimport CodeEditSourceEditor\n\n/// This class provides storage for semantic token data.\n///\n/// The LSP spec requires that clients keep the original compressed data to apply delta edits. Delta updates may\n/// appear as a delta to a single number in the compressed array. This class maintains the current state of compressed\n/// tokens and their decoded counterparts. It supports applying delta updates from the language server.\n///\n/// See ``SemanticTokenHighlightProvider`` for it's connection to the editor view.\nfinal class SemanticTokenStorage: GenericSemanticTokenStorage {\n    /// Represents compressed semantic token data received from a language server.\n    struct CurrentState {\n        let resultId: String?\n        let tokenData: [UInt32]\n        let tokens: [SemanticToken]\n    }\n\n    /// The last received result identifier.\n    var lastResultId: String? {\n        state?.resultId\n    }\n\n    /// Indicates if the storage object has received any data.\n    /// Once `setData` has been called, this returns `true`.\n    /// Other operations will fail without any data in the storage object.\n    var hasReceivedData: Bool {\n        state != nil\n    }\n\n    var state: CurrentState?\n\n    /// Create an empty storage object.\n    init() {\n        state = nil\n    }\n\n    // MARK: - Storage Conformance\n\n    /// Finds all tokens in the given range.\n    /// - Parameter range: The range to query.\n    /// - Returns: All tokens found in the range.\n    func getTokensFor(range: LSPRange) -> [SemanticToken] {\n        guard let state = state, !state.tokens.isEmpty else {\n            return []\n        }\n        var tokens: [SemanticToken] = []\n\n        // Perform a binary search\n        guard var idx = findLowerBound(in: range, data: state.tokens[...]) else {\n            return []\n        }\n\n        while idx < state.tokens.count && state.tokens[idx].startPosition < range.end {\n            tokens.append(state.tokens[idx])\n            idx += 1\n        }\n\n        return tokens\n    }\n\n    /// Clear the current state and set a new one.\n    /// - Parameter data: The semantic tokens to set as the current state.\n    func setData(_ data: borrowing SemanticTokens) {\n        state = CurrentState(resultId: data.resultId, tokenData: data.data, tokens: data.decode())\n    }\n\n    /// Apply a delta object from a language server and returns all token ranges that may need re-drawing.\n    ///\n    /// To calculate invalidated ranges:\n    /// - Grabs all semantic tokens that *will* be updated and invalidates their ranges\n    /// - Loops over all inserted tokens and invalidates their ranges\n    /// This may result in duplicated ranges. It's up to the caller to de-duplicate if necessary. See\n    /// ``SemanticTokenStorage/invalidatedRanges(startIdx:length:data:)``.\n    ///\n    /// - Parameter deltas: The deltas to apply.\n    /// - Returns: Ranges invalidated by the applied deltas.\n    func applyDelta(_ deltas: SemanticTokensDelta) -> [SemanticTokenRange] {\n        assert(state != nil, \"State should be set before applying any deltas.\")\n        guard var tokenData = state?.tokenData else { return [] }\n        var invalidatedSet: [SemanticTokenRange] = []\n\n        // Apply in reverse order (end to start)\n        for edit in deltas.edits.sorted(by: { $0.start > $1.start }) {\n            invalidatedSet.append(\n                contentsOf: invalidatedRanges(startIdx: edit.start, length: edit.deleteCount, data: tokenData[...])\n            )\n\n            // Apply to our copy of the tokens array\n            if edit.deleteCount > 0 {\n                tokenData.replaceSubrange(Int(edit.start)..<Int(edit.start + edit.deleteCount), with: edit.data ?? [])\n            } else {\n                tokenData.insert(contentsOf: edit.data ?? [], at: Int(edit.start))\n            }\n\n            if edit.data != nil {\n                invalidatedSet.append(\n                    contentsOf: invalidatedRanges(\n                        startIdx: edit.start,\n                        length: UInt(edit.data?.count ?? 0),\n                        data: tokenData[...]\n                    )\n                )\n            }\n        }\n\n        // Re-decode the updated token data and set the updated state\n        let decodedTokens = SemanticTokens(data: tokenData).decode()\n        state = CurrentState(resultId: deltas.resultId, tokenData: tokenData, tokens: decodedTokens)\n        return invalidatedSet\n    }\n\n    // MARK: - Invalidated Indices\n\n    /// Calculate what document ranges are invalidated due to changes in the compressed token data.\n    ///\n    /// This overestimates invalidated ranges by assuming all tokens touched by a change are invalid. All this does is\n    /// find what tokens are being updated by a delta and return them.\n    ///\n    /// - Parameters:\n    ///   - startIdx: The start index of the compressed token data an edits start at.\n    ///   - length: The length of any edits.\n    ///   - data: A reference to the compressed token data.\n    /// - Returns: All token ranges included in the range of the edit.\n    func invalidatedRanges(startIdx: UInt, length: UInt, data: ArraySlice<UInt32>) -> [SemanticTokenRange] {\n        var ranges: [SemanticTokenRange] = []\n        var idx = startIdx - (startIdx % 5)\n        while idx < startIdx + length {\n            ranges.append(\n                SemanticTokenRange(\n                    line: data[Int(idx)],\n                    char: data[Int(idx + 1)],\n                    length: data[Int(idx + 2)]\n                )\n            )\n            idx += 5\n        }\n        return ranges\n    }\n\n    // MARK: - Binary Search\n\n    /// Finds the lowest index of a `SemanticToken` that is entirely within the specified range.\n    /// - Complexity: Runs an **O(log n)** binary search on the data array.\n    /// - Parameters:\n    ///   - range: The range to search in, *not* inclusive.\n    ///   - data: The tokens to search. Takes an array slice to avoid unnecessary copying. This must be ordered by\n    ///           `startPosition`.\n    /// - Returns: The index in the data array of the lowest data element that lies within the given range, or `nil`\n    ///            if none are found.\n    func findLowerBound(in range: LSPRange, data: ArraySlice<SemanticToken>) -> Int? {\n        var low = 0\n        var high = data.count\n\n        // Find the first token with startPosition >= range.start.\n        while low < high {\n            let mid = low + (high - low) / 2\n            if data[mid].startPosition < range.start {\n                low = mid + 1\n            } else {\n                high = mid\n            }\n        }\n\n        // Return the item at `low` if it's valid.\n        if low < data.count && data[low].startPosition >= range.start && data[low].endPosition < range.end {\n            return low\n        }\n\n        return nil\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LSPUtil.swift",
    "content": "//\n//  LSPUtil.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/10/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\n\nenum LSPCompletionItemsUtil {\n\n    /// Helper function to get the edits from a completion item\n    /// - Parameters:\n    ///  - startPosition: The position where the completion was requested\n    ///  - item: The completion item\n    ///  - Returns: An array of TextEdit objects\n    static func getCompletionItemEdits(startPosition: Position, item: CompletionItem) -> [TextEdit] {\n        var edits: [TextEdit] = []\n\n        // If a TextEdit or InsertReplaceEdit value was provided\n        if let edit = item.textEdit {\n            editOrReplaceItem(edit: edit, &edits)\n        } else if let insertText = item.insertText {\n            // If the `insertText` value was provided\n            insertTextItem(startPosition: startPosition, insertText: insertText, &edits)\n        } else if !item.label.isEmpty {\n            // Fallback to the label\n            labelItem(startPosition: startPosition, label: item.label, &edits)\n        }\n\n        // If additional edits were provided\n        // An example would be to also include an 'import' statement at the top of the file\n        if let additionalEdits = item.additionalTextEdits {\n            edits.append(contentsOf: additionalEdits)\n        }\n\n        return edits\n    }\n\n    private static func editOrReplaceItem(edit: TwoTypeOption<TextEdit, InsertReplaceEdit>, _ edits: inout [TextEdit]) {\n        switch edit {\n        case .optionA(let textEdit):\n            edits.append(textEdit)\n        case .optionB(let insertReplaceEdit):\n            edits.append(\n                TextEdit(range: insertReplaceEdit.insert, newText: insertReplaceEdit.newText)\n            )\n            edits.append(\n                TextEdit(range: insertReplaceEdit.replace, newText: insertReplaceEdit.newText)\n            )\n        }\n    }\n\n    private static func insertTextItem(startPosition: Position, insertText: String, _ edits: inout [TextEdit]) {\n        let endPosition = Position((startPosition.line, startPosition.character + insertText.count))\n        edits.append(\n            TextEdit(\n                range: LSPRange(start: startPosition, end: endPosition),\n                newText: insertText\n            )\n        )\n    }\n\n    private static func labelItem(startPosition: Position, label: String, _ edits: inout [TextEdit]) {\n        let endPosition = Position((startPosition.line, startPosition.character + label.count))\n        edits.append(\n            TextEdit(\n                range: LSPRange(start: startPosition, end: endPosition),\n                newText: label\n            )\n        )\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+CallHierarchy.swift",
    "content": "//\n//  LanguageServer+CallHierarchy.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\n\nextension LanguageServer {\n    func requestPrepareCallHierarchy(\n        for documentURI: String, position: Position\n    ) async throws -> CallHierarchyPrepareResponse {\n        do {\n            let prepareParams = CallHierarchyPrepareParams(\n                textDocument: TextDocumentIdentifier(uri: documentURI),\n                position: position,\n                workDoneToken: nil\n            )\n            return try await lspInstance.prepareCallHierarchy(prepareParams)\n        } catch {\n            logger.warning(\"requestPrepareCallHierarchy: Error \\(error)\")\n            throw error\n        }\n    }\n\n    func requestCallHierarchyIncomingCalls(\n        _ callHierarchyItem: CallHierarchyItem\n    ) async throws -> CallHierarchyIncomingCallsResponse {\n        do {\n            let incomingParams = CallHierarchyIncomingCallsParams(\n                item: callHierarchyItem,\n                workDoneToken: nil\n            )\n            return try await lspInstance.callHierarchyIncomingCalls(incomingParams)\n        } catch {\n            logger.warning(\"requestCallHierarchyIncomingCalls: Error \\(error)\")\n            throw error\n        }\n    }\n\n    func requestCallHierarchyOutgoingCalls(\n        _ callHierarchyItem: CallHierarchyItem\n    ) async throws -> CallHierarchyOutgoingCallsResponse {\n        do {\n            let outgoingParams = CallHierarchyOutgoingCallsParams(\n                item: callHierarchyItem,\n                workDoneToken: nil\n            )\n            return try await lspInstance.callHierarchyOutgoingCalls(outgoingParams)\n        } catch {\n            logger.warning(\"requestCallHierarchyOutgoingCalls: Error \\(error)\")\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+ColorPresentation.swift",
    "content": "//\n//  LanguageServer+ColorPresentation.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\n\nextension LanguageServer {\n    func requestColorPresentation(\n        for documentURI: String,\n        color: Color,\n        range: LSPRange\n    ) async throws -> ColorPresentationResponse {\n        do {\n            let params = ColorPresentationParams(\n                workDoneToken: nil,\n                partialResultToken: nil,\n                textDocument: TextDocumentIdentifier(uri: documentURI),\n                color: color,\n                range: range\n            )\n            return try await lspInstance.colorPresentation(params)\n        } catch {\n            logger.warning(\"requestColorPresentation: Error \\(error)\")\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+Completion.swift",
    "content": "//\n//  LanguageServer+Completion.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\n\nextension LanguageServer {\n    func requestCompletion(for documentURI: String, position: Position) async throws -> CompletionResponse {\n        do {\n            let cacheKey = CacheKey(\n                uri: documentURI,\n                requestType: \"completion\",\n                extraData: position\n            )\n            if let cachedResponse: CompletionResponse = lspCache.get(key: cacheKey, as: CompletionResponse.self) {\n                return cachedResponse\n            }\n            let completionParams = CompletionParams(\n                uri: documentURI,\n                position: position,\n                triggerKind: .invoked,\n                triggerCharacter: nil\n            )\n            let response = try await lspInstance.completion(completionParams)\n\n            lspCache.set(key: cacheKey, value: response)\n            return response\n        } catch {\n            logger.warning(\"requestCompletion: Error \\(error)\")\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+Declaration.swift",
    "content": "//\n//  LanguageServer+Declaration.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\n\nextension LanguageServer {\n    func requestGoToDeclaration(for documentURI: String, position: Position) async throws -> DeclarationResponse {\n        do {\n            let params = TextDocumentPositionParams(\n                textDocument: TextDocumentIdentifier(uri: documentURI),\n                position: position\n            )\n            return try await lspInstance.declaration(params)\n        } catch {\n            logger.warning(\"requestGoToDeclaration: Error \\(error)\")\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+Definition.swift",
    "content": "//\n//  LanguageServer+Definition.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\n\nextension LanguageServer {\n    func requestGoToDefinition(for documentURI: String, position: Position) async throws -> DefinitionResponse {\n        do {\n            let cacheKey = CacheKey(\n                uri: documentURI,\n                requestType: \"goToDefinition\",\n                extraData: NoExtraData()\n            )\n            if let cachedResponse: DefinitionResponse = lspCache.get(key: cacheKey, as: DefinitionResponse.self) {\n                return cachedResponse\n            }\n\n            let textDocumentIdentifier = TextDocumentIdentifier(uri: documentURI)\n            let textDocumentPositionParams = TextDocumentPositionParams(\n                textDocument: textDocumentIdentifier,\n                position: position\n            )\n            let response = try await lspInstance.definition(textDocumentPositionParams)\n\n            lspCache.set(key: cacheKey, value: response)\n            return response\n        } catch {\n            logger.warning(\"requestGoToDefinition: Error \\(error)\")\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+Diagnostics.swift",
    "content": "//\n//  LanguageServer+Diagnostics.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\n\nextension LanguageServer {\n    func requestPullDiagnostics(document documentURI: String) async throws -> DocumentDiagnosticReport {\n        do {\n            let cacheKey = CacheKey(\n                uri: documentURI,\n                requestType: \"diagnostics\",\n                extraData: NoExtraData()\n            )\n            if let cachedResponse: DocumentDiagnosticReport = lspCache.get(\n                key: cacheKey, as: DocumentDiagnosticReport.self\n            ) {\n                return cachedResponse\n            }\n\n            let response = try await lspInstance.diagnostics(\n                DocumentDiagnosticParams(\n                    textDocument: TextDocumentIdentifier(uri: documentURI)\n                )\n            )\n            lspCache.set(key: cacheKey, value: response)\n            return response\n        } catch {\n            logger.warning(\"requestPullDiagnostics: Error \\(error)\")\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+DocumentColor.swift",
    "content": "//\n//  LanguageServer+DocumentColor.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\n\nextension LanguageServer {\n    /// The document color request is sent from the client to the server to list all color\n    /// references found in a given text document. Along with the range, a color value in RGB is returned.\n    /// Clients can use the result to decorate color references in an editor. For example:\n    ///     1. Color boxes showing the actual color next to the reference\n    ///     2. Show a color picker when a color reference is edited\n    func requestColor(for documentURI: String) async throws -> DocumentColorResponse {\n        let params = DocumentColorParams(\n            textDocument: TextDocumentIdentifier(uri: documentURI),\n            workDoneToken: nil,\n            partialResultToken: nil\n        )\n        do {\n            return try await lspInstance.documentColor(params)\n        } catch {\n            logger.warning(\"requestDocumentColor: Error \\(error)\")\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+DocumentHighlight.swift",
    "content": "//\n//  LanguageServer+DocumentHighlight.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport LanguageServerProtocol\n\nextension LanguageServer {\n    /// The document highlight request is sent from the client to the server to resolve document\n    /// highlights for a given text document position. For programming languages this usually\n    /// highlights all references to the symbol scoped to this file.\n    func requestHighlight(\n        for documentURI: String,\n        position: Position\n    ) async throws -> DocumentHighlightResponse {\n        do {\n            let params = DocumentHighlightParams(\n                textDocument: TextDocumentIdentifier(uri: documentURI),\n                position: position,\n                workDoneToken: nil,\n                partialResultToken: nil\n            )\n            return try await lspInstance.documentHighlight(params)\n        } catch {\n            logger.warning(\"requestDocumentHighlight Error: \\(error)\")\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+DocumentLink.swift",
    "content": "//\n//  LanguageServer+DocumentLink.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\n\n// TODO: DocumentLinkParams IS MISSING `textDocument: TextDocumentIdentifier;` FIELD IN LSP LIBRARY\n\nextension LanguageServer {\n    @available(*, deprecated, message: \"Not functional, see comment.\")\n    func requestLinkResolve(_ documentLink: DocumentLink) async throws -> DocumentLink? {\n        do {\n            return try await lspInstance.documentLinkResolve(documentLink)\n        } catch {\n            logger.warning(\"requestDocumentLinkResolve: Error \\(error)\")\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+DocumentSymbol.swift",
    "content": "//\n//  LanguageServer+DocumentSymbol.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\n\nextension LanguageServer {\n    func requestSymbols(for documentURI: String) async throws -> DocumentSymbolResponse {\n        do {\n            let textDocumentIdentifier = TextDocumentIdentifier(uri: documentURI)\n            let documentSymbolParams = DocumentSymbolParams(textDocument: textDocumentIdentifier)\n            return try await lspInstance.documentSymbol(documentSymbolParams)\n        } catch {\n            logger.warning(\"requestSymbols: Error \\(error)\")\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+DocumentSync.swift",
    "content": "//\n//  LanguageServer+DocumentSync.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\n\nextension LanguageServer {\n    /// Tells the language server we've opened a document and would like to begin working with it.\n    /// - Parameter document: The code document to open.\n    /// - Throws: Throws errors produced by the language server connection.\n    func openDocument(_ document: DocumentType) async throws {\n        do {\n            guard resolveOpenCloseSupport(), let content = await getIsolatedDocumentContent(document) else {\n                return\n            }\n            logger.debug(\"Opening Document \\(content.uri, privacy: .private)\")\n\n            openFiles.addDocument(document, for: self)\n\n            let textDocument = TextDocumentItem(\n                uri: content.uri,\n                languageId: content.language,\n                version: 0,\n                text: content.string\n            )\n            try await lspInstance.textDocumentDidOpen(DidOpenTextDocumentParams(textDocument: textDocument))\n\n            await updateIsolatedDocument(document)\n        } catch {\n            logger.warning(\"addDocument: Error \\(error)\")\n            throw error\n        }\n    }\n\n    /// Stops tracking a file and notifies the language server.\n    /// - Parameter uri: The URI of the document to close.\n    /// - Throws: Throws errors produced by the language server connection.\n    func closeDocument(_ uri: String) async throws {\n        do {\n            guard resolveOpenCloseSupport(), let document = openFiles.document(for: uri) else { return }\n            logger.debug(\"Closing document \\(uri, privacy: .private)\")\n\n            openFiles.removeDocument(for: uri)\n            await clearIsolatedDocument(document)\n\n            let params = DidCloseTextDocumentParams(textDocument: TextDocumentIdentifier(uri: uri))\n            try await lspInstance.textDocumentDidClose(params)\n        } catch {\n            logger.warning(\"closeDocument: Error \\(error)\")\n            throw error\n        }\n    }\n\n    /// Represents a single document edit event.\n    public struct DocumentChange: Sendable {\n        let range: LSPRange\n        let string: String\n\n        init(replacingContentsIn range: LSPRange, with string: String) {\n            self.range = range\n            self.string = string\n        }\n    }\n\n    /// Updates the document with the specified URI with new text and increments its version.\n    ///\n    /// This API accepts an array of changes to allow for grouping change notifications.\n    /// This is advantageous for full document changes as we reduce the number of times we send the entire document.\n    /// It also lowers some communication overhead when sending lots of changes very quickly due to sending them all in\n    /// one request.\n    ///\n    /// - Parameters:\n    ///   - uri: The URI of the document to update.\n    ///   - changes: An array of accumulated changes. It's suggested to throttle change notifications and send them\n    ///              in groups.\n    /// - Throws: Throws errors produced by the language server connection.\n    func documentChanged(uri: String, changes: [DocumentChange]) async throws {\n        do {\n            logger.debug(\"Document updated, \\(uri, privacy: .private)\")\n            guard let document = openFiles.document(for: uri) else { return }\n\n            switch resolveDocumentSyncKind() {\n            case .full:\n                guard let content = await getIsolatedDocumentContent(document) else {\n                    logger.error(\"Failed to get isolated document content\")\n                    return\n                }\n                let changeEvent = TextDocumentContentChangeEvent(range: nil, rangeLength: nil, text: content.string)\n                try await lspInstance.textDocumentDidChange(\n                    DidChangeTextDocumentParams(uri: uri, version: 0, contentChange: changeEvent)\n                )\n            case .incremental:\n                let fileVersion = openFiles.incrementVersion(for: uri)\n                let changeEvents = changes.map {\n                    // rangeLength is depreciated in the LSP spec.\n                    TextDocumentContentChangeEvent(range: $0.range, rangeLength: nil, text: $0.string)\n                }\n                try await lspInstance.textDocumentDidChange(\n                    DidChangeTextDocumentParams(uri: uri, version: fileVersion, contentChanges: changeEvents)\n                )\n            case .none:\n                return\n            }\n\n            // Let the semantic token provider know about the update.\n            // Note for future: If a related LSP object need notifying about document changes, do it here.\n            try await document.languageServerObjects.highlightProvider.documentDidChange()\n        } catch {\n            logger.warning(\"closeDocument: Error \\(error)\")\n            throw error\n        }\n    }\n\n    // MARK: File Private Helpers\n\n    /// Helper function for grabbing a document's content from the main actor.\n    @MainActor\n    private func getIsolatedDocumentContent(_ document: DocumentType) -> DocumentContent? {\n        guard let uri = document.languageServerURI,\n              let content = document.content?.string else {\n            return nil\n        }\n        return DocumentContent(uri: uri, language: document.getLanguage().id.rawValue, string: content)\n    }\n\n    @MainActor\n    private func updateIsolatedDocument(_ document: DocumentType) {\n        document.languageServerObjects.setUp(server: self, document: document)\n    }\n\n    @MainActor\n    private func clearIsolatedDocument(_ document: DocumentType) {\n        document.languageServerObjects = LanguageServerDocumentObjects()\n    }\n\n    // swiftlint:disable line_length\n    /// Determines the type of document sync the server supports.\n    /// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_synchronization_sc\n    fileprivate func resolveDocumentSyncKind() -> TextDocumentSyncKind {\n        // swiftlint:enable line_length\n        var syncKind: TextDocumentSyncKind = .none\n        switch serverCapabilities.textDocumentSync {\n        case .optionA(let options): // interface TextDocumentSyncOptions\n            syncKind = options.change ?? .none\n        case .optionB(let kind): // interface TextDocumentSyncKind\n            syncKind = kind\n        default:\n            syncKind = .none\n        }\n        return syncKind\n    }\n\n    /// Determines whether or not the server supports document tracking.\n    fileprivate func resolveOpenCloseSupport() -> Bool {\n        switch serverCapabilities.textDocumentSync {\n        case .optionA(let options): // interface TextDocumentSyncOptions\n            return options.openClose ?? false\n        case .optionB: // interface TextDocumentSyncKind\n            return true\n        default:\n            return true\n        }\n    }\n\n    // Used to avoid a lint error (`large_tuple`) for the return type of `getIsolatedDocumentContent`\n    fileprivate struct DocumentContent {\n        let uri: String\n        let language: String\n        let string: String\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+FoldingRange.swift",
    "content": "//\n//  LanguageServer+FoldingRange.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\n\nextension LanguageServer {\n    func requestFoldingRange(for documentURI: String) async throws -> FoldingRangeResponse {\n        do {\n            let params = FoldingRangeParams(textDocument: TextDocumentIdentifier(uri: documentURI))\n            return try await lspInstance.foldingRange(params)\n        } catch {\n            logger.warning(\"requestFoldingRange: Error \\(error)\")\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+Formatting.swift",
    "content": "//\n//  LanguageServer+Formatting.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\n\nextension LanguageServer {\n    func requestFormatting(\n        for documentURI: String,\n        withFormat formattingOptions: FormattingOptions\n    ) async throws -> FormattingResult {\n        do {\n            let params = DocumentFormattingParams(\n                textDocument: TextDocumentIdentifier(uri: documentURI),\n                options: formattingOptions\n            )\n            return try await lspInstance.formatting(params)\n        } catch {\n            logger.warning(\"requestFormatting: Error \\(error)\")\n            throw error\n        }\n    }\n\n    func requestRangeFormatting(\n        for documentURI: String,\n        _ range: LSPRange,\n        withFormat formattingOptions: FormattingOptions\n    ) async throws -> FormattingResult {\n        do {\n            let params = DocumentRangeFormattingParams(\n                textDocument: TextDocumentIdentifier(uri: documentURI),\n                range: range,\n                options: formattingOptions\n            )\n            return try await lspInstance.rangeFormatting(params)\n        } catch {\n            logger.warning(\"requestRangeFormatting: Error \\(error)\")\n            throw error\n        }\n    }\n\n    func requestOnTypeFormatting(\n        for documentURI: String,\n        _ position: Position,\n        character char: String,\n        withFormat formattingOptions: FormattingOptions\n    ) async throws -> FormattingResult {\n        do {\n            let params = DocumentOnTypeFormattingParams(\n                textDocument: TextDocumentIdentifier(uri: documentURI),\n                position: position,\n                ch: char,\n                options: formattingOptions\n            )\n            return try await lspInstance.onTypeFormatting(params)\n        } catch {\n            logger.warning(\"requestOnTypeFormatting: Error \\(error)\")\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+Hover.swift",
    "content": "//\n//  LanguageServer+Hover.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\n\nextension LanguageServer {\n    /// The hover request is sent from the client to the server to request hover\n    /// information at a given text document position.\n    func requestHover(for documentURI: String, _ position: Position) async throws -> HoverResponse {\n        do {\n            let params = TextDocumentPositionParams(\n                textDocument: TextDocumentIdentifier(uri: documentURI),\n                position: position\n            )\n            return try await lspInstance.hover(params)\n        } catch {\n            logger.warning(\"requestHover: Error \\(error)\")\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+Implementation.swift",
    "content": "//\n//  LanguageServer+Implementation.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\n\nextension LanguageServer {\n    /// Resolve the implementation location of a symbol at a given text document position\n    func requestImplementation(for documentURI: String, _ position: Position) async throws -> ImplementationResponse {\n        do {\n            let params = TextDocumentPositionParams(\n                textDocument: TextDocumentIdentifier(uri: documentURI),\n                position: position\n            )\n            return try await lspInstance.implementation(params)\n        } catch {\n            logger.warning(\"requestImplementation: Error \\(error)\")\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+InlayHint.swift",
    "content": "//\n//  LanguageServer+InlayHint.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\n\nextension LanguageServer {\n    /// Compute inlay hints for a given [text document, range] tuple that may be rendered in the\n    /// editor in place with other text\n    func requestInlayHint(for documentURI: String, _ range: LSPRange) async throws -> InlayHintResponse {\n        do {\n            let params = InlayHintParams(\n                workDoneToken: nil,\n                textDocument: TextDocumentIdentifier(uri: documentURI),\n                range: range\n            )\n            return try await lspInstance.inlayHint(params)\n        } catch {\n            logger.warning(\"requestInlayHint: Error \\(error)\")\n            throw error\n        }\n    }\n\n    /// The request is sent from the client to the server to resolve additional information for a given inlay hint.\n    /// This is usually used to compute the tooltip, location or command properties of an inlay hint’s label part\n    /// to avoid its unnecessary computation during the textDocument/inlayHint request.\n    func requestInlayHintResolve(_ inlayHint: InlayHint) async throws -> InlayHint? {\n        do {\n            return try await lspInstance.inlayHintResolve(inlayHint)\n        } catch {\n            logger.warning(\"requestInlayHintResolve: Error \\(error)\")\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+References.swift",
    "content": "//\n//  LanguageServer+References.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\n\nextension LanguageServer {\n    /// Resolve project-wide references for the symbol denoted by the given text document position\n    func requestFindReferences(\n        for documentURI: String,\n        _ position: Position,\n        _ includeDeclaration: Bool = false\n    ) async throws -> ReferenceResponse {\n        do {\n            let params = ReferenceParams(\n                textDocument: TextDocumentIdentifier(uri: documentURI),\n                position: position,\n                includeDeclaration: includeDeclaration\n            )\n            return try await lspInstance.references(params)\n        } catch {\n            logger.warning(\"requestFindReferences: Error \\(error)\")\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+Rename.swift",
    "content": "//\n//  LanguageServer+Rename.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport LanguageServerProtocol\n\nextension LanguageServer {\n    /// Setup and test the validity of a rename operation at a given location\n    func requestPrepareRename(for documentURI: String, _ position: Position) async throws -> PrepareRenameResponse {\n        do {\n            let params = TextDocumentPositionParams(\n                textDocument: TextDocumentIdentifier(uri: documentURI),\n                position: position\n            )\n            return try await lspInstance.prepareRename(params)\n        } catch {\n            logger.warning(\"requestPrepareRename: Error \\(error)\")\n            throw error\n        }\n    }\n\n    /// Ask the server to compute a workspace change so that the client can perform a workspace-wide rename of a symbol\n    func requestRename(\n        for documentURI: String,\n        position: Position,\n        newName name: String\n    ) async throws -> RenameResponse {\n        do {\n            let params = RenameParams(\n                textDocument: TextDocumentIdentifier(uri: documentURI),\n                position: position,\n                newName: name\n            )\n            return try await lspInstance.rename(params)\n        } catch {\n            logger.warning(\"requestRename: Error \\(error)\")\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+SelectionRange.swift",
    "content": "//\n//  LanguageServer+SelectionRange.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\n\nextension LanguageServer {\n    /// Setup and test the validity of a rename operation at a given location\n    func requestSelectionRange(for documentURI: String, positions: [Position]) async throws -> SelectionRangeResponse {\n        do {\n            let params = SelectionRangeParams(\n                workDoneToken: nil,\n                textDocument: TextDocumentIdentifier(uri: documentURI),\n                positions: positions\n            )\n            return try await lspInstance.selectionRange(params)\n        } catch {\n            logger.warning(\"requestSelectionRange: Error \\(error)\")\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+SemanticTokens.swift",
    "content": "//\n//  LanguageServer+SemanticTokens.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\n\nextension LanguageServer {\n    func requestSemanticTokens(for documentURI: String) async throws -> SemanticTokensResponse {\n        do {\n            let params = SemanticTokensParams(textDocument: TextDocumentIdentifier(uri: documentURI))\n            return try await lspInstance.semanticTokensFull(params)\n        } catch {\n            logger.warning(\"requestSemanticTokens full: Error \\(error)\")\n            throw error\n        }\n    }\n\n    func requestSemanticTokens(\n        for documentURI: String,\n        previousResultId: String\n    ) async throws -> SemanticTokensDeltaResponse {\n        do {\n            let params = SemanticTokensDeltaParams(\n                textDocument: TextDocumentIdentifier(uri: documentURI),\n                previousResultId: previousResultId\n            )\n            return try await lspInstance.semanticTokensFullDelta(params)\n        } catch {\n            logger.warning(\"requestSemanticTokens versioned: Error \\(error)\")\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+SignatureHelp.swift",
    "content": "//\n//  LanguageServer+SignatureHelp.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\n\nextension LanguageServer {\n    /// Request signature information at a given cursor position\n    func requestSignatureHelp(for documentURI: String, _ position: Position) async throws -> SignatureHelpResponse {\n        do {\n            let params = TextDocumentPositionParams(\n                textDocument: TextDocumentIdentifier(uri: documentURI),\n                position: position\n            )\n            return try await lspInstance.signatureHelp(params)\n        } catch {\n            logger.warning(\"requestInlayHint: Error \\(error)\")\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+TypeDefinition.swift",
    "content": "//\n//  LanguageServer+TypeDefinition.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\n\nextension LanguageServer {\n    /// Resolve the type definition location of a symbol at a given text document position\n    func requestTypeDefinition(for documentURI: String, _ position: Position) async throws -> TypeDefinitionResponse {\n        do {\n            let params = TextDocumentPositionParams(\n                textDocument: TextDocumentIdentifier(uri: documentURI),\n                position: position\n            )\n            return try await lspInstance.typeDefinition(params)\n        } catch {\n            logger.warning(\"requestTypeDefinition: Error \\(error)\")\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/LSPCache+Data.swift",
    "content": "//\n//  LSPCache+Data.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 6/23/24.\n//\n\nimport Foundation\n\nstruct NoExtraData: Hashable { }\n\nstruct CacheKey: Hashable {\n    let uri: String\n    let requestType: String\n    let extraData: AnyHashable?\n\n    init(uri: String, requestType: String, extraData: AnyHashable? = nil) {\n        self.uri = uri\n        self.requestType = requestType\n        self.extraData = extraData\n    }\n\n    static func == (lhs: CacheKey, rhs: CacheKey) -> Bool {\n        return lhs.uri == rhs.uri &&\n            lhs.requestType == rhs.requestType &&\n            lhs.extraData == rhs.extraData\n    }\n\n    func hash(into hasher: inout Hasher) {\n        hasher.combine(uri)\n        hasher.combine(requestType)\n        if let extraData = extraData {\n            hasher.combine(extraData)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/LSPCache.swift",
    "content": "//\n//  LSPCache.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport Foundation\n\nclass LSPCache {\n    private var cache = NSCache<NSString, CacheEntry>()\n\n    func get<T: Codable & Sendable>(key: CacheKey, as type: T.Type) -> T? {\n        let cacheKey = key.description as NSString\n        guard let entry = cache.object(forKey: cacheKey) else {\n            return nil\n        }\n        return entry.getValue(as: type)\n    }\n\n    func set<T: Codable & Sendable>(key: CacheKey, value: T) {\n        let entry = CacheEntry(value)\n        let cacheKey = key.description as NSString\n        cache.setObject(entry, forKey: cacheKey)\n    }\n\n    func invalidate(key: CacheKey) {\n        let cacheKey = key.description as NSString\n        cache.removeObject(forKey: cacheKey)\n    }\n\n    /// Represents a single cache entry with a generic type.\n    final class CacheEntry: NSObject {\n        let value: Any\n        let type: Any.Type\n\n        init<T: Codable & Sendable>(_ value: T) {\n            self.value = value\n            self.type = T.self\n        }\n\n        func getValue<T: Codable & Sendable>(as type: T.Type) -> T? {\n            return value as? T\n        }\n    }\n}\n\nextension CacheKey: CustomStringConvertible {\n    var description: String {\n        if let extraData = extraData {\n            return \"\\(uri)-\\(requestType)-\\(extraData)\"\n        } else {\n            return \"\\(uri)-\\(requestType)\"\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/LanguageServer.swift",
    "content": "//\n//  LanguageServer.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport JSONRPC\nimport Foundation\nimport LanguageClient\nimport LanguageServerProtocol\nimport OSLog\n\n/// A client for language servers.\nclass LanguageServer<DocumentType: LanguageServerDocument> {\n    static var logger: Logger { // types with associated types cannot have constant static properties\n        Logger(subsystem: Bundle.main.bundleIdentifier ?? \"\", category: \"LanguageServer\")\n    }\n    let logger: Logger\n\n    /// Identifies which language the server belongs to\n    let languageId: LanguageIdentifier\n    /// Holds information about the language server binary\n    let binary: LanguageServerBinary\n    /// A cache to hold responses from the server, to minimize duplicate server requests\n    let lspCache = LSPCache()\n\n    /// Tracks documents and their associated objects.\n    /// Use this property when adding new objects that need to track file data, or have a state associated with the\n    /// language server and a document. For example, the content coordinator.\n    let openFiles: LanguageServerFileMap<DocumentType>\n\n    /// Maps the language server's highlight config to one CodeEdit can read. See ``SemanticTokenMap``.\n    let highlightMap: SemanticTokenMap?\n\n    /// The configuration options this server supports.\n    var serverCapabilities: ServerCapabilities\n\n    var logContainer: LanguageServerLogContainer\n\n    /// An instance of a language server, that may or may not be initialized\n    private(set) var lspInstance: InitializingServer\n    /// The path to the root of the project\n    private(set) var rootPath: URL\n    /// The PID of the running language server process.\n    private(set) var pid: pid_t\n\n    init(\n        languageId: LanguageIdentifier,\n        binary: LanguageServerBinary,\n        lspInstance: InitializingServer,\n        lspPid: pid_t,\n        serverCapabilities: ServerCapabilities,\n        rootPath: URL,\n        logContainer: LanguageServerLogContainer\n    ) {\n        self.languageId = languageId\n        self.binary = binary\n        self.lspInstance = lspInstance\n        self.pid = lspPid\n        self.serverCapabilities = serverCapabilities\n        self.rootPath = rootPath\n        self.openFiles = LanguageServerFileMap()\n        self.logContainer = logContainer\n        self.logger = Logger(\n            subsystem: Bundle.main.bundleIdentifier ?? \"\",\n            category: \"LanguageServer.\\(languageId.rawValue)\"\n        )\n        if let semanticTokensProvider = serverCapabilities.semanticTokensProvider {\n            self.highlightMap = SemanticTokenMap(semanticCapability: semanticTokensProvider)\n        } else {\n            self.highlightMap = nil // Server doesn't support semantic highlights\n        }\n    }\n\n    /// Creates and initializes a language server.\n    /// - Parameters:\n    ///   - languageId: The id of the language to create.\n    ///   - binary: The binary where the language server is stored.\n    ///   - workspacePath: The path of the workspace being opened.\n    /// - Returns: An initialized language server.\n    static func createServer(\n        for languageId: LanguageIdentifier,\n        with binary: LanguageServerBinary,\n        workspacePath: String\n    ) async throws -> LanguageServer {\n        let executionParams = Process.ExecutionParameters(\n            path: binary.execPath,\n            arguments: binary.args,\n            environment: binary.env\n        )\n\n        let logContainer = LanguageServerLogContainer(language: languageId)\n        let (connection, process) = try makeLocalServerConnection(\n            languageId: languageId,\n            executionParams: executionParams,\n            logContainer: logContainer\n        )\n        let server = InitializingServer(\n            server: connection,\n            initializeParamsProvider: getInitParams(workspacePath: workspacePath)\n        )\n        let initializationResponse = try await server.initializeIfNeeded()\n\n        return LanguageServer(\n            languageId: languageId,\n            binary: binary,\n            lspInstance: server,\n            lspPid: process.processIdentifier,\n            serverCapabilities: initializationResponse.capabilities,\n            rootPath: URL(filePath: workspacePath),\n            logContainer: logContainer\n        )\n    }\n\n    // MARK: - Make Local Server Connection\n\n    /// Creates a data channel for sending and receiving data with an LSP.\n    /// - Parameters:\n    ///   - languageId: The ID of the language to create the channel for.\n    ///   - executionParams: The parameters for executing the local process.\n    /// - Returns: A new connection to the language server.\n    static func makeLocalServerConnection(\n        languageId: LanguageIdentifier,\n        executionParams: Process.ExecutionParameters,\n        logContainer: LanguageServerLogContainer\n    ) throws -> (connection: JSONRPCServerConnection, process: Process) {\n        do {\n            let (channel, process) = try DataChannel.localProcessChannel(\n                parameters: executionParams,\n                terminationHandler: { [weak logContainer] in\n                    logger.debug(\"Terminated data channel for \\(languageId.rawValue)\")\n                    logContainer?.appendLog(\n                        LogMessageParams(type: .error, message: \"Data Channel Terminated Unexpectedly\")\n                    )\n                }\n            )\n            return (JSONRPCServerConnection(dataChannel: channel), process)\n        } catch {\n            logger.warning(\"Failed to initialize data channel for \\(languageId.rawValue)\")\n            throw error\n        }\n    }\n\n    // MARK: - Get Init Params\n\n    // swiftlint:disable function_body_length\n    static func getInitParams(workspacePath: String) -> InitializingServer.InitializeParamsProvider {\n        let provider: InitializingServer.InitializeParamsProvider = {\n            // Text Document Capabilities\n            let textDocumentCapabilities = TextDocumentClientCapabilities(\n                completion: CompletionClientCapabilities(\n                    dynamicRegistration: true,\n                    completionItem: CompletionClientCapabilities.CompletionItem(\n                        snippetSupport: true,\n                        commitCharactersSupport: true,\n                        documentationFormat: [MarkupKind.plaintext],\n                        deprecatedSupport: true,\n                        preselectSupport: true,\n                        tagSupport: ValueSet(valueSet: [CompletionItemTag.deprecated]),\n                        insertReplaceSupport: true,\n                        resolveSupport: CompletionClientCapabilities.CompletionItem.ResolveSupport(\n                            properties: [\"documentation\", \"details\"]\n                        ),\n                        insertTextModeSupport: ValueSet(valueSet: [InsertTextMode.adjustIndentation]),\n                        labelDetailsSupport: true\n                    ),\n                    completionItemKind: ValueSet(valueSet: [CompletionItemKind.text, CompletionItemKind.method]),\n                    contextSupport: true,\n                    insertTextMode: InsertTextMode.asIs,\n                    completionList: CompletionClientCapabilities.CompletionList(\n                        itemDefaults: [\"default1\", \"default2\"]\n                    )\n                ),\n                // swiftlint:disable:next line_length\n                // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#semanticTokensClientCapabilities\n                semanticTokens: SemanticTokensClientCapabilities(\n                    dynamicRegistration: false,\n                    requests: .init(range: false, delta: true),\n                    tokenTypes: SemanticTokenTypes.allStrings,\n                    tokenModifiers: SemanticTokenModifiers.allStrings,\n                    formats: [.relative],\n                    overlappingTokenSupport: true,\n                    multilineTokenSupport: true,\n                    serverCancelSupport: false,\n                    augmentsSyntaxTokens: true\n                )\n            )\n\n            // Workspace File Operations Capabilities\n            let fileOperations = ClientCapabilities.Workspace.FileOperations(\n                dynamicRegistration: true,\n                didCreate: true,\n                willCreate: true,\n                didRename: true,\n                willRename: true,\n                didDelete: true,\n                willDelete: true\n            )\n\n            // Workspace Capabilities\n            let workspaceCapabilities = ClientCapabilities.Workspace(\n                applyEdit: true,\n                workspaceEdit: nil,\n                didChangeConfiguration: DidChangeConfigurationClientCapabilities(dynamicRegistration: true),\n                didChangeWatchedFiles: DidChangeWatchedFilesClientCapabilities(dynamicRegistration: true),\n                symbol: WorkspaceSymbolClientCapabilities(\n                    dynamicRegistration: true,\n                    symbolKind: nil,\n                    tagSupport: nil,\n                    resolveSupport: []\n                ),\n                executeCommand: nil,\n                workspaceFolders: true,\n                configuration: true,\n                semanticTokens: nil,\n                codeLens: nil,\n                fileOperations: fileOperations\n            )\n\n            let windowClientCapabilities = WindowClientCapabilities(\n                workDoneProgress: true,\n                showMessage: ShowMessageRequestClientCapabilities(\n                    messageActionItem: ShowMessageRequestClientCapabilities.MessageActionItemCapabilities(\n                        additionalPropertiesSupport: true\n                    )\n                ),\n                showDocument: ShowDocumentClientCapabilities(\n                    support: true\n                )\n            )\n\n            // All Client Capabilities\n            let capabilities = ClientCapabilities(\n                workspace: workspaceCapabilities,\n                textDocument: textDocumentCapabilities,\n                window: windowClientCapabilities,\n                general: nil,\n                experimental: nil\n            )\n             return InitializeParams(\n                processId: nil,\n                locale: nil,\n                rootPath: nil,\n                rootUri: \"file://\" + workspacePath, // Make it a URI\n                initializationOptions: [],\n                capabilities: capabilities,\n                trace: nil,\n                workspaceFolders: nil\n             )\n        }\n        return provider\n        // swiftlint:enable function_body_length\n    }\n\n    // MARK: - Shutdown\n\n    /// Shuts down the language server and exits it.\n    public func shutdown() async throws {\n        self.logger.info(\"Shutting down language server\")\n        try await self.lspInstance.shutdownAndExit()\n    }\n}\n\n/// Represents a language server binary.\nstruct LanguageServerBinary: Codable {\n    /// The path to the language server binary.\n    let execPath: String\n    /// The arguments to pass to the language server binary.\n    let args: [String]\n    /// The environment variables to pass to the language server binary.\n    let env: [String: String]?\n}\n\nenum LSPError: Error {\n    case binaryNotFound\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServer/LanguageServerFileMap.swift",
    "content": "//\n//  LanguageServerFileMap.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 9/8/24.\n//\n\nimport Foundation\nimport LanguageServerProtocol\n\n/// Tracks data associated with files and language servers.\nclass LanguageServerFileMap<DocumentType: LanguageServerDocument> {\n    typealias HighlightProviderType = SemanticTokenHighlightProvider<SemanticTokenStorage, DocumentType>\n\n    /// Extend this struct as more objects are associated with a code document.\n    private struct DocumentObject {\n        let uri: String\n        var documentVersion: Int\n    }\n\n    private var trackedDocuments: NSMapTable<NSString, DocumentType>\n    private var trackedDocumentData: [String: DocumentObject] = [:]\n\n    init() {\n        trackedDocuments = NSMapTable<NSString, DocumentType>(valueOptions: [.weakMemory])\n    }\n\n    // MARK: - Track & Remove Documents\n\n    func addDocument(_ document: DocumentType, for server: LanguageServer<DocumentType>) {\n        guard let uri = document.languageServerURI else { return }\n        trackedDocuments.setObject(document, forKey: uri as NSString)\n        let docData = DocumentObject(uri: uri, documentVersion: 0)\n        trackedDocumentData[uri] = docData\n    }\n\n    func document(for uri: DocumentUri) -> DocumentType? {\n        return trackedDocuments.object(forKey: uri as NSString)\n    }\n\n    func removeDocument(for document: DocumentType) {\n        guard let uri = document.languageServerURI else { return }\n        removeDocument(for: uri)\n    }\n\n    func removeDocument(for uri: DocumentUri) {\n        trackedDocuments.removeObject(forKey: uri as NSString)\n        trackedDocumentData.removeValue(forKey: uri)\n    }\n\n    // MARK: - Version Number Tracking\n\n    func incrementVersion(for document: DocumentType) -> Int {\n        guard let uri = document.languageServerURI else { return 0 }\n        return incrementVersion(for: uri)\n    }\n\n    func incrementVersion(for uri: DocumentUri) -> Int {\n        trackedDocumentData[uri]?.documentVersion += 1\n        return trackedDocumentData[uri]?.documentVersion ?? 0\n    }\n\n    func documentVersion(for document: DocumentType) -> Int? {\n        guard let uri = document.languageServerURI else { return nil }\n        return documentVersion(for: uri)\n    }\n\n    func documentVersion(for uri: DocumentUri) -> Int? {\n        return trackedDocumentData[uri]?.documentVersion\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/LanguageServerDocument.swift",
    "content": "//\n//  LanguageServerDocument.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 2/12/25.\n//\n\nimport AppKit\nimport CodeEditLanguages\n\n/// A set of properties a language server sets when a document is registered.\nstruct LanguageServerDocumentObjects<DocumentType: LanguageServerDocument> {\n    var textCoordinator: LSPContentCoordinator<DocumentType> = LSPContentCoordinator()\n    // swiftlint:disable:next line_length\n    var highlightProvider: SemanticTokenHighlightProvider<SemanticTokenStorage, DocumentType> = SemanticTokenHighlightProvider()\n\n    @MainActor\n    func setUp(server: LanguageServer<DocumentType>, document: DocumentType) {\n        textCoordinator.setUp(server: server, document: document)\n        highlightProvider.setUp(server: server, document: document)\n    }\n}\n\n/// A protocol that allows a language server to register objects on a text document.\nprotocol LanguageServerDocument: AnyObject {\n    var content: NSTextStorage? { get }\n    var languageServerURI: String? { get }\n    var languageServerObjects: LanguageServerDocumentObjects<Self> { get set }\n    func getLanguage() -> CodeLanguage\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/Errors/PackageManagerError.swift",
    "content": "//\n//  PackageManagerError.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 5/12/25.\n//\n\nimport Foundation\n\nenum PackageManagerError: Error, LocalizedError {\n    case unknown\n    case packageManagerNotInstalled\n    case initializationFailed(String)\n    case installationFailed(String)\n    case invalidConfiguration\n\n    var errorDescription: String? {\n        switch self {\n        case .unknown:\n            \"Unknown error occurred\"\n        case .packageManagerNotInstalled:\n            \"The required package manager is not installed.\"\n        case .initializationFailed:\n            \"Installation directory initialization failed.\"\n        case .installationFailed:\n            \"Package installation failed.\"\n        case .invalidConfiguration:\n            \"The package registry contained an invalid installation configuration.\"\n        }\n    }\n\n    var failureReason: String? {\n        switch self {\n        case .unknown:\n            nil\n        case .packageManagerNotInstalled:\n            nil\n        case .initializationFailed(let string):\n            string\n        case .installationFailed(let string):\n            string\n        case .invalidConfiguration:\n            nil\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/Errors/RegistryManagerError.swift",
    "content": "//\n//  RegistryManagerError.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 5/12/25.\n//\n\nimport Foundation\n\nenum RegistryManagerError: Error, LocalizedError {\n    case installationRunning\n    case invalidResponse(statusCode: Int)\n    case downloadFailed(url: URL, error: Error)\n    case maxRetriesExceeded(url: URL, lastError: Error)\n    case writeFailed(error: Error)\n    case failedToSaveRegistryCache\n\n    var errorDescription: String? {\n        switch self {\n        case .installationRunning:\n            \"A package is already being installed.\"\n        case .invalidResponse(let statusCode):\n            \"Invalid response received: \\(statusCode)\"\n        case .downloadFailed(let url, _):\n            \"Download for \\(url) error.\"\n        case .maxRetriesExceeded(let url, _):\n            \"Maximum retries exceeded for url: \\(url)\"\n        case .writeFailed:\n            \"Failed to write to file.\"\n        case .failedToSaveRegistryCache:\n            \"Failed to write to registry cache.\"\n        }\n    }\n\n    var failureReason: String? {\n        switch self {\n        case .installationRunning, .invalidResponse, .failedToSaveRegistryCache:\n            return nil\n        case .downloadFailed(_, let error), .maxRetriesExceeded(_, let error), .writeFailed(let error):\n            return if let error = error as? LocalizedError {\n                error.errorDescription\n            } else {\n                error.localizedDescription\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/Model/InstallationMethod.swift",
    "content": "//\n//  InstallationMethod.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 5/12/25.\n//\n\nimport Foundation\n\n/// Installation method enum with all supported types\nenum InstallationMethod: Equatable {\n    /// For standard package manager installations\n    case standardPackage(source: PackageSource)\n    /// For packages that need to be built from source with custom build steps\n    case sourceBuild(source: PackageSource, command: String)\n    /// For direct binary downloads\n    case binaryDownload(source: PackageSource, url: URL)\n    /// For installations that aren't recognized\n    case unknown\n\n    var packageName: String? {\n        switch self {\n        case .standardPackage(let source),\n             .sourceBuild(let source, _),\n             .binaryDownload(let source, _):\n            return source.pkgName\n        case .unknown:\n            return nil\n        }\n    }\n\n    var version: String? {\n        switch self {\n        case .standardPackage(let source),\n             .sourceBuild(let source, _),\n             .binaryDownload(let source, _):\n            return source.version\n        case .unknown:\n            return nil\n        }\n    }\n\n    var packageManagerType: PackageManagerType? {\n        switch self {\n        case .standardPackage(let source),\n             .sourceBuild(let source, _),\n             .binaryDownload(let source, _):\n            return source.type\n        case .unknown:\n            return nil\n        }\n    }\n\n    func packageManager(installPath: URL) -> PackageManagerProtocol? {\n        switch packageManagerType {\n        case .npm:\n            return NPMPackageManager(installationDirectory: installPath)\n        case .cargo:\n            return CargoPackageManager(installationDirectory: installPath)\n        case .pip:\n            return PipPackageManager(installationDirectory: installPath)\n        case .golang:\n            return GolangPackageManager(installationDirectory: installPath)\n        case .github, .sourceBuild:\n            return GithubPackageManager(installationDirectory: installPath)\n        case .nuget, .opam, .gem, .composer:\n            // TODO: IMPLEMENT OTHER PACKAGE MANAGERS\n            return nil\n        default:\n            return nil\n        }\n    }\n\n    var installerDescription: String {\n        guard let packageManagerType else { return \"Unknown\" }\n        switch packageManagerType {\n        case .npm, .cargo, .golang, .pip, .sourceBuild, .github:\n            return packageManagerType.userDescription\n        case .nuget, .opam, .gem, .composer:\n            return \"(Unsupported) \\(packageManagerType.userDescription)\"\n        }\n    }\n\n    var packageDescription: String? {\n        guard let packageName else { return nil }\n        if let version {\n            return \"\\(packageName)@\\(version)\"\n        }\n        return packageName\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/Model/PackageManagerType.swift",
    "content": "//\n//  PackageManagerType.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 5/12/25.\n//\n\n/// Package manager types supported by the system\nenum PackageManagerType: String, Codable {\n    /// JavaScript\n    case npm\n    /// Rust\n    case cargo\n    /// Go\n    case golang\n    /// Python\n    case pip\n    /// Ruby\n    case gem\n    /// C#\n    case nuget\n    /// OCaml\n    case opam\n    /// PHP\n    case composer\n    /// Building from source\n    case sourceBuild\n    /// Binary download\n    case github\n\n    var userDescription: String {\n        switch self {\n        case .npm:\n            \"NPM\"\n        case .cargo:\n            \"Cargo\"\n        case .golang:\n            \"Go\"\n        case .pip:\n            \"Pip\"\n        case .gem:\n            \"Gem\"\n        case .nuget:\n            \"Nuget\"\n        case .opam:\n            \"Opam\"\n        case .composer:\n            \"Composer\"\n        case .sourceBuild:\n            \"Build From Source\"\n        case .github:\n            \"Download From GitHub\"\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/Model/PackageSource.swift",
    "content": "//\n//  PackageSource.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 8/18/25.\n//\n\n/// Generic package source information that applies to all installation methods.\n/// Takes all the necessary information from `RegistryItem`.\nstruct PackageSource: Equatable, Codable {\n    /// The raw source ID string from the registry\n    let sourceId: String\n    /// The type of the package manager\n    let type: PackageManagerType\n    /// Package name\n    let pkgName: String\n    /// The name in the registry.json file. Used for the folder name when saved.\n    let entryName: String\n    /// Package version\n    let version: String\n    /// URL for repository or download link\n    let repositoryUrl: String?\n    /// Git reference type if this is a git based package\n    let gitReference: GitReference?\n    /// Additional possible options\n    var options: [String: String]\n\n    init(\n        sourceId: String,\n        type: PackageManagerType,\n        pkgName: String,\n        entryName: String,\n        version: String,\n        repositoryUrl: String? = nil,\n        gitReference: GitReference? = nil,\n        options: [String: String] = [:]\n    ) {\n        self.sourceId = sourceId\n        self.type = type\n        self.pkgName = pkgName\n        self.entryName = entryName\n        self.version = version\n        self.repositoryUrl = repositoryUrl\n        self.gitReference = gitReference\n        self.options = options\n    }\n\n    enum GitReference: Equatable, Codable {\n        case tag(String)\n        case revision(String)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/Model/RegistryItem+Source.swift",
    "content": "//\n//  RegistryItem+Source.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 8/15/25.\n//\n\nextension RegistryItem {\n    struct Source: Codable {\n        let id: String\n        let asset: AssetContainer?\n        let build: BuildContainer?\n        let versionOverrides: [VersionOverride]?\n\n        enum AssetContainer: Codable {\n            case single(Asset)\n            case multiple([Asset])\n            case simpleFile(String)\n            case none\n\n            init(from decoder: Decoder) throws {\n                if let container = try? decoder.singleValueContainer() {\n                    if let singleValue = try? container.decode(Asset.self) {\n                        self = .single(singleValue)\n                        return\n                    } else if let multipleValues = try? container.decode([Asset].self) {\n                        self = .multiple(multipleValues)\n                        return\n                    } else if let simpleFile = try? container.decode([String: String].self),\n                              simpleFile.count == 1,\n                              simpleFile.keys.contains(\"file\"),\n                              let file = simpleFile[\"file\"] {\n                        self = .simpleFile(file)\n                        return\n                    }\n                }\n                self = .none\n            }\n\n            func encode(to encoder: Encoder) throws {\n                var container = encoder.singleValueContainer()\n                switch self {\n                case .single(let value):\n                    try container.encode(value)\n                case .multiple(let values):\n                    try container.encode(values)\n                case .simpleFile(let file):\n                    try container.encode([\"file\": file])\n                case .none:\n                    try container.encodeNil()\n                }\n            }\n\n            func getDarwinFileName() -> String? {\n                switch self {\n                case .single(let asset):\n                    if asset.target.isDarwinTarget() {\n                        return asset.file\n                    }\n\n                case .multiple(let assets):\n                    for asset in assets where asset.target.isDarwinTarget() {\n                        return asset.file\n                    }\n\n                case .simpleFile(let fileName):\n                    return fileName\n\n                case .none:\n                    return nil\n                }\n                return nil\n            }\n        }\n\n        enum BuildContainer: Codable {\n            case single(Build)\n            case multiple([Build])\n            case none\n\n            init(from decoder: Decoder) throws {\n                if let container = try? decoder.singleValueContainer() {\n                    if let singleValue = try? container.decode(Build.self) {\n                        self = .single(singleValue)\n                        return\n                    } else if let multipleValues = try? container.decode([Build].self) {\n                        self = .multiple(multipleValues)\n                        return\n                    }\n                }\n                self = .none\n            }\n\n            func encode(to encoder: Encoder) throws {\n                var container = encoder.singleValueContainer()\n                switch self {\n                case .single(let value):\n                    try container.encode(value)\n                case .multiple(let values):\n                    try container.encode(values)\n                case .none:\n                    try container.encodeNil()\n                }\n            }\n\n            func getUnixBuildCommand() -> String? {\n                switch self {\n                case .single(let build):\n                    return build.run\n                case .multiple(let builds):\n                    for build in builds {\n                        guard let target = build.target else { continue }\n                        if target.isDarwinTarget() {\n                            return build.run\n                        }\n                    }\n                case .none:\n                    return nil\n                }\n                return nil\n            }\n        }\n\n        struct Build: Codable {\n            let target: Target?\n            let run: String\n            let env: [String: String]?\n            let bin: BinContainer?\n        }\n\n        struct Asset: Codable {\n            let target: Target\n            let file: String?\n            let bin: BinContainer?\n\n            init(from decoder: Decoder) throws {\n                let container = try decoder.container(keyedBy: CodingKeys.self)\n                self.target = try container.decode(Target.self, forKey: .target)\n                self.file = try container.decodeIfPresent(String.self, forKey: .file)\n                self.bin = try container.decodeIfPresent(BinContainer.self, forKey: .bin)\n            }\n        }\n\n        enum Target: Codable {\n            case single(String)\n            case multiple([String])\n\n            init(from decoder: Decoder) throws {\n                let container = try decoder.singleValueContainer()\n                if let singleValue = try? container.decode(String.self) {\n                    self = .single(singleValue)\n                } else if let multipleValues = try? container.decode([String].self) {\n                    self = .multiple(multipleValues)\n                } else {\n                    throw DecodingError.typeMismatch(\n                        Target.self,\n                        DecodingError.Context(\n                            codingPath: decoder.codingPath,\n                            debugDescription: \"Invalid target format\"\n                        )\n                    )\n                }\n            }\n\n            func encode(to encoder: Encoder) throws {\n                var container = encoder.singleValueContainer()\n                switch self {\n                case .single(let value):\n                    try container.encode(value)\n                case .multiple(let values):\n                    try container.encode(values)\n                }\n            }\n\n            func isDarwinTarget() -> Bool {\n                switch self {\n                case .single(let value):\n#if arch(arm64)\n                    return value == \"darwin\" || value == \"darwin_arm64\" || value == \"unix\"\n#else\n                    return value == \"darwin\" || value == \"darwin_x64\" || value == \"unix\"\n#endif\n                case .multiple(let values):\n#if arch(arm64)\n                    return values.contains(\"darwin\") ||\n                    values.contains(\"darwin_arm64\") ||\n                    values.contains(\"unix\")\n#else\n                    return values.contains(\"darwin\") ||\n                    values.contains(\"darwin_x64\") ||\n                    values.contains(\"unix\")\n#endif\n                }\n            }\n        }\n\n        enum BinContainer: Codable {\n            case single(String)\n            case multiple([String: String])\n\n            init(from decoder: Decoder) throws {\n                let container = try decoder.singleValueContainer()\n                if let singleValue = try? container.decode(String.self) {\n                    self = .single(singleValue)\n                } else if let dictValue = try? container.decode([String: String].self) {\n                    self = .multiple(dictValue)\n                } else {\n                    throw DecodingError.typeMismatch(\n                        BinContainer.self,\n                        DecodingError.Context(\n                            codingPath: decoder.codingPath,\n                            debugDescription: \"Invalid bin format\"\n                        )\n                    )\n                }\n            }\n\n            func encode(to encoder: Encoder) throws {\n                var container = encoder.singleValueContainer()\n                switch self {\n                case .single(let value):\n                    try container.encode(value)\n                case .multiple(let values):\n                    try container.encode(values)\n                }\n            }\n        }\n\n        struct VersionOverride: Codable {\n            let constraint: String\n            let id: String\n            let asset: AssetContainer?\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/Model/RegistryItem.swift",
    "content": "//\n//  RegistryItem.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 1/29/25.\n//\n\nimport Foundation\n\n/// A `RegistryItem` represents an entry in the Registry that saves language servers, DAPs, linters and formatters.\nstruct RegistryItem: Codable {\n    let name: String\n    let description: String\n    let homepage: String\n    let licenses: [String]\n    let languages: [String]\n    let categories: [String]\n    let source: Source\n    let bin: [String: String]?\n\n    var sanitizedName: String {\n        name.replacingOccurrences(of: \"-\", with: \" \")\n            .replacingOccurrences(of: \"_\", with: \" \")\n            .split(separator: \" \")\n            .map { word -> String in\n                let str = String(word).lowercased()\n                // Check for special cases\n                if str == \"ls\" || str == \"lsp\" || str == \"ci\" || str == \"cli\" {\n                    return str.uppercased()\n                }\n                return str.capitalized\n            }\n            .joined(separator: \" \")\n    }\n\n    var sanitizedDescription: String {\n        description.replacingOccurrences(of: \"\\n\", with: \" \")\n    }\n\n    var homepageURL: URL? {\n        URL(string: homepage)\n    }\n\n    /// A pretty version of the homepage URL.\n    /// Removes the schema (eg https) and leaves the path and domain.\n    var homepagePretty: String {\n        guard let homepageURL else { return homepage }\n        return (homepageURL.host(percentEncoded: false) ?? \"\") + homepageURL.path(percentEncoded: false)\n    }\n\n    /// The method for installation, parsed from this item's ``source-swift.property`` parameter.\n    var installMethod: InstallationMethod? {\n        let sourceId = source.id\n        if sourceId.hasPrefix(\"pkg:cargo/\") {\n            return PackageSourceParser.parseCargoPackage(self)\n        } else if sourceId.hasPrefix(\"pkg:npm/\") {\n            return PackageSourceParser.parseNpmPackage(self)\n        } else if sourceId.hasPrefix(\"pkg:pypi/\") {\n            return PackageSourceParser.parsePythonPackage(self)\n        } else if sourceId.hasPrefix(\"pkg:gem/\") {\n            return PackageSourceParser.parseRubyGem(self)\n        } else if sourceId.hasPrefix(\"pkg:golang/\") {\n            return PackageSourceParser.parseGolangPackage(self)\n        } else if sourceId.hasPrefix(\"pkg:github/\") {\n            return PackageSourceParser.parseGithubPackage(self)\n        } else {\n            return nil\n        }\n    }\n\n    /// Serializes back to JSON format\n    func toDictionary() throws -> [String: Any] {\n        let data = try JSONEncoder().encode(self)\n        let jsonObject = try JSONSerialization.jsonObject(with: data)\n        guard let dictionary = jsonObject as? [String: Any] else {\n            throw NSError(domain: \"ConversionError\", code: 1)\n        }\n        return dictionary\n    }\n}\n\nextension RegistryItem: FuzzySearchable {\n    var searchableString: String { name }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/PackageManagerProtocol.swift",
    "content": "//\n//  PackageManager.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/2/25.\n//\n\nimport Foundation\n\n/// The protocol each package manager conforms to for creating ``PackageManagerInstallOperation``s.\nprotocol PackageManagerProtocol {\n    var shellClient: ShellClient { get }\n\n    /// Calls the shell commands to install a package\n    func install(method installationMethod: InstallationMethod) throws -> [PackageManagerInstallStep]\n    /// Gets the location of the binary that was installed\n    func getBinaryPath(for package: String) -> String\n    /// Checks if the shell commands for the package manager are available or not\n    func isInstalled(method installationMethod: InstallationMethod) -> PackageManagerInstallStep\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/PackageManagers/Install/InstallStepConfirmation.swift",
    "content": "//\n//  InstallStepConfirmation.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 8/8/25.\n//\n\nenum InstallStepConfirmation {\n    case none\n    case required(message: String)\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/PackageManagers/Install/PackageManagerInstallOperation.swift",
    "content": "//\n//  PackageManagerInstallOperation.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 8/8/25.\n//\n\nimport Foundation\nimport Combine\n\n/// An executable install operation for installing a ``RegistryItem``.\n///\n/// Has a single entry point, ``run()``, which kicks off the operation. UI can observe properties like the ``error``,\n/// ``runningState-swift.property``, or ``currentStep`` to monitor progress.\n///\n/// If a step requires confirmation, the ``waitingForConfirmation`` value will be filled.\n@MainActor\nfinal class PackageManagerInstallOperation: ObservableObject, Identifiable {\n    enum RunningState {\n        case none\n        case running\n        case complete\n    }\n\n    struct OutputItem: Identifiable, Equatable {\n        let id: UUID = UUID()\n        let isStepDivider: Bool\n        let outputIdx: Int?\n        let contents: String\n\n        init(outputIdx: Int? = nil, isStepDivider: Bool = false, contents: String) {\n            self.isStepDivider = isStepDivider\n            self.outputIdx = outputIdx\n            self.contents = contents\n        }\n    }\n\n    nonisolated var id: String { package.name }\n\n    let package: RegistryItem\n    let steps: [PackageManagerInstallStep]\n\n    /// The step the operation is currently executing or stopped at.\n    var currentStep: PackageManagerInstallStep? {\n        steps[safe: currentStepIdx]\n    }\n\n    /// The current state of the operation.\n    var runningState: RunningState {\n        if operationTask != nil {\n            return .running\n        } else if error != nil || currentStepIdx == steps.count {\n            return .complete\n        } else {\n            return .none\n        }\n    }\n\n    @Published var accumulatedOutput: [OutputItem] = []\n    @Published var currentStepIdx: Int = 0\n    @Published var error: Error?\n    @Published var progress: Progress\n\n    /// If non-nil, indicates that this operation has halted and requires confirmation.\n    @Published public private(set) var waitingForConfirmation: String?\n\n    private let shellClient: ShellClient = .live()\n    private var operationTask: Task<Void, Error>?\n    private var confirmationContinuation: CheckedContinuation<Void, Never>?\n    private var outputIdx = 0\n\n    /// Create a new operation using a list of steps and a package description.\n    /// See ``PackageManagerProtocol`` for \n    /// - Parameters:\n    ///   - package: The package to install.\n    ///   - steps: The steps that make up the operation.\n    init(package: RegistryItem, steps: [PackageManagerInstallStep]) {\n        self.package = package\n        self.steps = steps\n        self.progress = Progress(totalUnitCount: Int64(steps.count))\n    }\n\n    func run() async throws {\n        guard operationTask == nil else { return }\n        operationTask = Task {\n            defer { operationTask = nil }\n            try await runNext()\n        }\n        try await operationTask?.value\n    }\n\n    func cancel() {\n        operationTask?.cancel()\n        operationTask = nil\n    }\n\n    /// Called by UI to confirm continuing to the next step\n    func confirmCurrentStep() {\n        waitingForConfirmation = nil\n        confirmationContinuation?.resume()\n        confirmationContinuation = nil\n    }\n\n    private func waitForConfirmation(message: String) async {\n        waitingForConfirmation = message\n        await withCheckedContinuation { [weak self] (continuation: CheckedContinuation<Void, Never>) in\n            self?.confirmationContinuation = continuation\n        }\n    }\n\n    private func runNext() async throws {\n        guard currentStepIdx < steps.count, error == nil else {\n            return\n        }\n\n        let task = steps[currentStepIdx]\n\n        switch task.confirmation {\n        case .required(let message):\n            await waitForConfirmation(message: message)\n        case .none:\n            break\n        }\n\n        let model = PackageManagerProgressModel(shellClient: shellClient)\n        progress.addChild(model.progress, withPendingUnitCount: 1)\n\n        try Task.checkCancellation()\n        accumulatedOutput.append(OutputItem(isStepDivider: true, contents: \"Step \\(currentStepIdx + 1): \\(task.name)\"))\n\n        await withTaskGroup(of: Void.self) { group in\n            group.addTask {\n                for await outputItem in model.outputStream {\n                    await MainActor.run {\n                        switch outputItem {\n                        case .status(let string):\n                            self.outputIdx += 1\n                            self.accumulatedOutput.append(OutputItem(outputIdx: self.outputIdx, contents: string))\n                        case .output(let string):\n                            self.accumulatedOutput.append(OutputItem(contents: string))\n                        }\n                    }\n                }\n            }\n            group.addTask {\n                do {\n                    try await task.handler(model)\n                } catch {\n                    await MainActor.run {\n                        self.error = error\n                    }\n                }\n                await MainActor.run {\n                    model.finish()\n                }\n            }\n        }\n\n        self.currentStepIdx += 1\n\n        try Task.checkCancellation()\n        if let error {\n            progress.cancel()\n            throw error\n        }\n        try await runNext()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/PackageManagers/Install/PackageManagerInstallStep.swift",
    "content": "//\n//  PackageManagerInstallStep.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 8/18/25.\n//\n\n/// Represents a single executable step in a package install.\nstruct PackageManagerInstallStep: Identifiable {\n    var id: String { name }\n    let name: String\n    let confirmation: InstallStepConfirmation\n    let handler: (_ model: PackageManagerProgressModel) async throws -> Void\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/PackageManagers/Install/PackageManagerProgressModel.swift",
    "content": "//\n//  PackageManagerProgressModel.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 8/8/25.\n//\n\nimport Foundation\nimport Combine\n\n/// This model is injected into each ``PackageManagerInstallStep`` when executing a ``PackageManagerInstallOperation``.\n/// A single model is used for each step. Output is collected by the ``PackageManagerInstallOperation``.\n///\n/// Yields output into an async stream, and provides common helper methods for exec-ing commands, creating\n/// directories, etc.\n@MainActor\nfinal class PackageManagerProgressModel: ObservableObject {\n    enum OutputItem {\n        case status(String)\n        case output(String)\n    }\n\n    let outputStream: AsyncStream<OutputItem>\n    @Published var progress: Progress\n\n    private let shellClient: ShellClient\n    private let outputContinuation: AsyncStream<OutputItem>.Continuation\n\n    init(shellClient: ShellClient) {\n        self.shellClient = shellClient\n        self.progress = Progress(totalUnitCount: 1)\n        (outputStream, outputContinuation) = AsyncStream<OutputItem>.makeStream()\n    }\n\n    func status(_ string: String) {\n        outputContinuation.yield(.status(string))\n    }\n\n    /// Creates the directory for the language server to be installed in\n    func createDirectoryStructure(for packagePath: URL) throws {\n        let decodedPath = packagePath.path(percentEncoded: false)\n        if FileManager.default.fileExists(atPath: decodedPath) {\n            status(\"Removing existing installation.\")\n            try FileManager.default.removeItem(at: packagePath)\n        }\n\n        status(\"Creating directory: \\(decodedPath)\")\n        try FileManager.default.createDirectory(\n            at: packagePath,\n            withIntermediateDirectories: true,\n            attributes: nil\n        )\n    }\n\n    /// Executes commands in the specified directory\n    @discardableResult\n    func executeInDirectory(in packagePath: String, _ args: [String]) async throws -> [String] {\n        return try await runCommand(\"cd \\\"\\(packagePath)\\\" && \\(args.joined(separator: \" \"))\")\n    }\n\n    /// Runs a shell command and returns output\n    @discardableResult\n    func runCommand(_ command: String) async throws -> [String] {\n        var output: [String] = []\n        status(\"\\(command)\")\n        for try await line in shellClient.runAsync(command) {\n            output.append(line)\n            outputContinuation.yield(.output(line))\n        }\n        return output\n    }\n\n    func finish() {\n        outputContinuation.finish()\n        progress.completedUnitCount = progress.totalUnitCount\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/PackageManagers/Sources/CargoPackageManager.swift",
    "content": "//\n//  CargoPackageManager.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/3/25.\n//\n\nimport Foundation\n\nfinal class CargoPackageManager: PackageManagerProtocol {\n    private let installationDirectory: URL\n\n    let shellClient: ShellClient\n\n    init(installationDirectory: URL) {\n        self.installationDirectory = installationDirectory\n        self.shellClient = .live()\n    }\n\n    func install(method installationMethod: InstallationMethod) throws -> [PackageManagerInstallStep] {\n        guard case .standardPackage(let source) = installationMethod else {\n            throw PackageManagerError.invalidConfiguration\n        }\n        let packagePath = installationDirectory.appending(path: source.entryName)\n        return [\n            initialize(in: packagePath),\n            runCargoInstall(source, in: packagePath)\n        ]\n    }\n\n    func isInstalled(method installationMethod: InstallationMethod) -> PackageManagerInstallStep {\n        PackageManagerInstallStep(\n            name: \"\",\n            confirmation: .none\n        ) { model in\n            let versionOutput = try await model.runCommand(\"cargo --version\")\n            let output = versionOutput.reduce(into: \"\") {\n                $0 += $1.trimmingCharacters(in: .whitespacesAndNewlines)\n            }\n            guard output.starts(with: \"cargo\") else {\n                throw PackageManagerError.packageManagerNotInstalled\n            }\n        }\n    }\n\n    func getBinaryPath(for package: String) -> String {\n        return installationDirectory.appending(path: package).appending(path: \"bin\").path\n    }\n\n    func initialize(in packagePath: URL) -> PackageManagerInstallStep {\n        PackageManagerInstallStep(name: \"Initialize Directory Structure\", confirmation: .none) { model in\n            try await model.createDirectoryStructure(for: packagePath)\n        }\n    }\n\n    func runCargoInstall(_ source: PackageSource, in packagePath: URL) -> PackageManagerInstallStep {\n        let qualifiedPackageName = \"\\(source.pkgName)@\\(source.version)\"\n\n        return PackageManagerInstallStep(\n            name: \"Install Package Using cargo\",\n            confirmation: .required(\n                message: \"This requires the cargo package \\(qualifiedPackageName).\"\n                + \"\\nAllow CodeEdit to install this package?\"\n            )\n        ) { model in\n            var cargoArgs = [\"cargo\", \"install\", \"--root\", \".\"]\n\n            // If this is a git-based package\n            if let gitRef = source.gitReference, let repoUrl = source.repositoryUrl {\n                cargoArgs.append(contentsOf: [\"--git\", repoUrl])\n                switch gitRef {\n                case .tag(let tag):\n                    cargoArgs.append(contentsOf: [\"--tag\", tag])\n                case .revision(let rev):\n                    cargoArgs.append(contentsOf: [\"--rev\", rev])\n                }\n            } else {\n                cargoArgs.append(qualifiedPackageName)\n            }\n\n            if let features = source.options[\"features\"] {\n                cargoArgs.append(contentsOf: [\"--features\", features])\n            }\n            if source.options[\"locked\"] == \"true\" {\n                cargoArgs.append(\"--locked\")\n            }\n\n            try await model.executeInDirectory(in: packagePath.path(percentEncoded: false), cargoArgs)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/PackageManagers/Sources/GithubPackageManager.swift",
    "content": "//\n//  GithubPackageManager.swift\n//  LSPInstallTest\n//\n//  Created by Abe Malla on 3/10/25.\n//\n\nimport Foundation\n\nfinal class GithubPackageManager: PackageManagerProtocol {\n    private let installationDirectory: URL\n\n    let shellClient: ShellClient\n\n    init(installationDirectory: URL) {\n        self.installationDirectory = installationDirectory\n        self.shellClient = .live()\n    }\n\n    // MARK: - PackageManagerProtocol\n\n    func install(method installationMethod: InstallationMethod) throws -> [PackageManagerInstallStep] {\n        switch installationMethod {\n        case let .binaryDownload(source, url):\n            let packagePath = installationDirectory.appending(path: source.entryName)\n            return [\n                initialize(in: packagePath),\n                downloadBinary(source, url: url, installDir: installationDirectory),\n                decompressBinary(source, url: url, installDir: installationDirectory)\n            ]\n        case let .sourceBuild(source, command):\n            let packagePath = installationDirectory.appending(path: source.entryName)\n            return [\n                initialize(in: packagePath),\n                try gitClone(source, installDir: installationDirectory),\n                installFromSource(source, installDir: installationDirectory, command: command)\n            ]\n        case .standardPackage, .unknown:\n            throw PackageManagerError.invalidConfiguration\n        }\n    }\n\n    func isInstalled(method installationMethod: InstallationMethod) -> PackageManagerInstallStep {\n        switch installationMethod {\n        case .binaryDownload:\n            PackageManagerInstallStep(\n                name: \"\",\n                confirmation: .none,\n                handler: { _ in }\n            )\n        case .sourceBuild:\n            PackageManagerInstallStep(\n                name: \"\",\n                confirmation: .required(\n                    message: \"This package requires git to install. Allow CodeEdit to run git commands?\"\n                )\n            ) { model in\n                let versionOutput = try await model.runCommand(\"git --version\")\n                let output = versionOutput.reduce(into: \"\") {\n                    $0 += $1.trimmingCharacters(in: .whitespacesAndNewlines)\n                }\n                guard output.contains(\"git version\") else {\n                    throw PackageManagerError.packageManagerNotInstalled\n                }\n            }\n        case .standardPackage, .unknown:\n            PackageManagerInstallStep(\n                name: \"\",\n                confirmation: .none,\n                handler: { _ in throw PackageManagerError.invalidConfiguration }\n            )\n        }\n    }\n\n    func getBinaryPath(for package: String) -> String {\n        return installationDirectory.appending(path: package).appending(path: \"bin\").path\n    }\n\n    // MARK: - Initialize\n\n    func initialize(in packagePath: URL) -> PackageManagerInstallStep {\n        PackageManagerInstallStep(\n            name: \"Initialize Directory Structure\",\n            confirmation: .none\n        ) { model in\n            do {\n                try await model.createDirectoryStructure(for: packagePath)\n            } catch {\n                throw PackageManagerError.initializationFailed(error.localizedDescription)\n            }\n        }\n    }\n\n    // MARK: - Download Binary\n\n    private func downloadBinary(\n        _ source: PackageSource,\n        url: URL,\n        installDir installationDirectory: URL\n    ) -> PackageManagerInstallStep {\n        PackageManagerInstallStep(\n            name: \"Download Binary Executable\",\n            confirmation: .none\n        ) { model in\n            do {\n                await model.status(\"Downloading \\(url)\")\n                let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 120.0)\n                // TODO: Progress Updates\n                let (tempURL, response) = try await URLSession.shared.download(for: request)\n\n                guard let httpResponse = response as? HTTPURLResponse,\n                      (200...299).contains(httpResponse.statusCode) else {\n                    throw RegistryManagerError.downloadFailed(\n                        url: url,\n                        error: NSError(domain: \"HTTP error\", code: (response as? HTTPURLResponse)?.statusCode ?? -1)\n                    )\n                }\n\n                let fileName = url.lastPathComponent\n                let downloadPath = installationDirectory.appending(path: source.entryName)\n                let packagePath = downloadPath.appending(path: fileName)\n                if FileManager.default.fileExists(atPath: packagePath.path()) {\n                    try FileManager.default.removeItem(at: packagePath)\n                }\n\n                try FileManager.default.moveItem(at: tempURL, to: packagePath)\n\n                if !FileManager.default.fileExists(atPath: packagePath.path) {\n                    throw RegistryManagerError.downloadFailed(\n                        url: url,\n                        error: NSError(domain: \"Could not download package\", code: -1)\n                    )\n                }\n            } catch {\n                if error is RegistryManagerError {\n                    // Keep error info for known errors thrown here.\n                    throw error\n                }\n                throw RegistryManagerError.downloadFailed(\n                    url: url,\n                    error: error\n                )\n            }\n\n        }\n    }\n\n    // MARK: - Decompress Binary\n\n    private func decompressBinary(\n        _ source: PackageSource,\n        url: URL,\n        installDir installationDirectory: URL\n    ) -> PackageManagerInstallStep {\n        PackageManagerInstallStep(\n            name: \"Decompress Binary Executable\",\n            confirmation: .none,\n        ) { model in\n            let fileName = url.lastPathComponent\n            let downloadPath = installationDirectory.appending(path: source.entryName)\n            let packagePath = downloadPath.appending(path: fileName)\n\n            if packagePath.pathExtension == \"tar\" || packagePath.pathExtension == \".zip\" {\n                await model.status(\"Decompressing \\(fileName)\")\n                try await FileManager.default.unzipItem(at: packagePath, to: downloadPath, progress: model.progress)\n                if FileManager.default.fileExists(atPath: packagePath.path(percentEncoded: false)) {\n                    try FileManager.default.removeItem(at: packagePath)\n                }\n                await model.status(\"Decompressed to '\\(downloadPath.path(percentEncoded: false))'\")\n            } else if packagePath.lastPathComponent.hasSuffix(\".tar.gz\") {\n                await model.status(\"Decompressing \\(fileName) using `tar`\")\n                _ = try await model.executeInDirectory(\n                    in: packagePath.deletingLastPathComponent().path(percentEncoded: false),\n                    [\n                        \"tar\",\n                        \"-xzf\",\n                        packagePath.path(percentEncoded: false).escapedDirectory(),\n                    ]\n                )\n            } else if packagePath.pathExtension == \"gz\" {\n                await model.status(\"Decompressing \\(fileName) using `gunzip`\")\n                _ = try await model.executeInDirectory(\n                    in: packagePath.deletingLastPathComponent().path(percentEncoded: false),\n                    [\n                        \"gunzip\",\n                        \"-f\",\n                        packagePath.path(percentEncoded: false).escapedDirectory(),\n                    ]\n                )\n            }\n\n            let executablePath = downloadPath.appending(path: url.deletingPathExtension().lastPathComponent)\n            try FileManager.default.makeExecutable(executablePath)\n        }\n    }\n\n    // MARK: - Git Clone\n\n    private func gitClone(\n        _ source: PackageSource,\n        installDir installationDirectory: URL\n    ) throws -> PackageManagerInstallStep {\n        guard let repoURL = source.repositoryUrl else {\n            throw PackageManagerError.invalidConfiguration\n        }\n        let command = [\"git\", \"clone\", repoURL]\n\n        return PackageManagerInstallStep(\n            name: \"Clone with Git\",\n            // swiftlint:disable:next line_length\n            confirmation: .required(message: \"This step will run the following command to clone the package from source control:\\n`\\(command.joined(separator: \" \"))`\")\n        ) { model in\n            let installPath = installationDirectory.appending(path: source.entryName, directoryHint: .isDirectory)\n            _ = try await model.executeInDirectory(in: installPath.path, command)\n        }\n    }\n\n    // MARK: - Install From Source\n\n    private func installFromSource(\n        _ source: PackageSource,\n        installDir installationDirectory: URL,\n        command: String\n    ) -> PackageManagerInstallStep {\n        PackageManagerInstallStep(\n            name: \"Install From Source\",\n            confirmation: .required(message: \"This step will run the following to finish installing:\\n`\\(command)`\")\n        ) { model in\n            do {\n                let installPath = installationDirectory.appending(path: source.entryName, directoryHint: .isDirectory)\n                let repoPath = installPath.appending(path: source.pkgName, directoryHint: .isDirectory)\n                _ = try await model.executeInDirectory(in: repoPath.path, [command])\n            } catch {\n                throw PackageManagerError.installationFailed(\"Source build failed.\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/PackageManagers/Sources/GolangPackageManager.swift",
    "content": "//\n//  GolangPackageManager.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/3/25.\n//\n\nimport Foundation\n\nfinal class GolangPackageManager: PackageManagerProtocol {\n    private let installationDirectory: URL\n\n    let shellClient: ShellClient\n\n    init(installationDirectory: URL) {\n        self.installationDirectory = installationDirectory\n        self.shellClient = .live()\n    }\n\n    // MARK: - PackageManagerProtocol\n\n    func install(method installationMethod: InstallationMethod) throws -> [PackageManagerInstallStep] {\n        guard case .standardPackage(let source) = installationMethod else {\n            throw PackageManagerError.invalidConfiguration\n        }\n\n        let packagePath = installationDirectory.appending(path: source.entryName)\n        var steps = [\n            initialize(in: packagePath),\n            runGoInstall(source, packagePath: packagePath)\n        ]\n\n        if source.options[\"subpath\"] != nil {\n            steps.append(buildBinary(source, packagePath: packagePath))\n        }\n\n        return steps\n    }\n\n    /// Check if go is installed\n    func isInstalled(method installationMethod: InstallationMethod) -> PackageManagerInstallStep {\n        PackageManagerInstallStep(\n            name: \"\",\n            confirmation: .required(message: \"This package requires go to install. Allow CodeEdit to run go commands?\")\n        ) { model in\n            let versionOutput = try await model.runCommand(\"go version\")\n            let versionPattern = #\"go version go\\d+\\.\\d+\"#\n            let output = versionOutput.reduce(into: \"\") {\n                $0 += $1.trimmingCharacters(in: .whitespacesAndNewlines)\n            }\n            guard output.range(of: versionPattern, options: .regularExpression) != nil else {\n                throw PackageManagerError.packageManagerNotInstalled\n            }\n        }\n    }\n\n    /// Get the binary path for a Go package\n    func getBinaryPath(for package: String) -> String {\n        let binPath = installationDirectory.appending(path: package).appending(path: \"bin\")\n        let binaryName = package.components(separatedBy: \"/\").last ?? package\n        let specificBinPath = binPath.appending(path: binaryName).path\n        if FileManager.default.fileExists(atPath: specificBinPath) {\n            return specificBinPath\n        }\n        return binPath.path\n    }\n\n    // MARK: - Initialize\n\n    func initialize(in packagePath: URL) -> PackageManagerInstallStep {\n        PackageManagerInstallStep(\n            name: \"Initialize Directory Structure\",\n            confirmation: .none\n        ) { model in\n            try await model.createDirectoryStructure(for: packagePath)\n\n            // For Go, we need to set up a proper module structure\n            let goModPath = packagePath.appending(path: \"go.mod\")\n            if !FileManager.default.fileExists(atPath: goModPath.path) {\n                let moduleName = \"codeedit.temp/placeholder\"\n                _ = try await model.executeInDirectory(\n                    in: packagePath.path, [\"go mod init \\(moduleName)\"]\n                )\n            }\n        }\n    }\n\n    // MARK: - Install Using Go\n\n    func runGoInstall(_ source: PackageSource, packagePath: URL) -> PackageManagerInstallStep {\n        let installCommand = getGoInstallCommand(source)\n        return PackageManagerInstallStep(\n            name: \"Install Package Using go\",\n            confirmation: .required(\n                message: \"This requires installing the go package \\(installCommand).\"\n                + \"\\nAllow CodeEdit to install this package?\"\n            )\n        ) { model in\n            let gobinPath = packagePath.appending(path: \"bin\", directoryHint: .isDirectory).path\n            var goInstallCommand = [\"env\", \"GOBIN=\\(gobinPath)\", \"go\", \"install\"]\n\n            goInstallCommand.append(installCommand)\n            _ = try await model.executeInDirectory(in: packagePath.path, goInstallCommand)\n        }\n    }\n\n    // MARK: - Build Binary\n\n    func buildBinary(_ source: PackageSource, packagePath: URL) -> PackageManagerInstallStep {\n        PackageManagerInstallStep(\n            name: \"Build From Source\",\n            confirmation: .none\n        ) { model in\n            // If there's a subpath, build the binary\n            if let subpath = source.options[\"subpath\"] {\n                let binPath = packagePath.appending(path: \"bin\")\n                if !FileManager.default.fileExists(atPath: binPath.path) {\n                    try FileManager.default.createDirectory(at: binPath, withIntermediateDirectories: true)\n                }\n\n                let binaryName = subpath.components(separatedBy: \"/\").last ??\n                source.pkgName.components(separatedBy: \"/\").last ?? source.pkgName\n                let buildArgs = [\"go\", \"build\", \"-o\", \"bin/\\(binaryName)\"]\n\n                // If source.pkgName includes the full import path (like github.com/owner/repo)\n                if source.pkgName.contains(\"/\") {\n                    _ = try await model.executeInDirectory(\n                        in: packagePath.path, buildArgs + [\"\\(source.pkgName)/\\(subpath)\"]\n                    )\n                } else {\n                    _ = try await model.executeInDirectory(\n                        in: packagePath.path, buildArgs + [subpath]\n                    )\n                }\n                let execPath = packagePath.appending(path: \"bin\").appending(path: binaryName)\n                try FileManager.default.makeExecutable(execPath)\n            }\n        }\n    }\n\n    // MARK: - Helper methods\n\n    /// Clean up after a failed installation\n    private func cleanupFailedInstallation(packagePath: URL) throws {\n        let goSumPath = packagePath.appending(path: \"go.sum\")\n        if FileManager.default.fileExists(atPath: goSumPath.path) {\n            try FileManager.default.removeItem(at: goSumPath)\n        }\n    }\n\n    private func getGoInstallCommand(_ source: PackageSource) -> String {\n        if let gitRef = source.gitReference, let repoUrl = source.repositoryUrl {\n            // Check if this is a Git-based package\n            var packageName = source.pkgName\n            if !packageName.contains(\"github.com\") && !packageName.contains(\"golang.org\") {\n                packageName = repoUrl.replacingOccurrences(of: \"https://\", with: \"\")\n            }\n\n            var gitVersion: String\n            switch gitRef {\n            case .tag(let tag):\n                gitVersion = tag\n            case .revision(let rev):\n                gitVersion = rev\n            }\n\n            return \"\\(packageName)@\\(gitVersion)\"\n        } else {\n            return \"\\(source.pkgName)@\\(source.version)\"\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/PackageManagers/Sources/NPMPackageManager.swift",
    "content": "//\n//  NPMPackageManager.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/2/25.\n//\n\nimport Foundation\n\nfinal class NPMPackageManager: PackageManagerProtocol {\n    private let installationDirectory: URL\n\n    let shellClient: ShellClient\n\n    init(installationDirectory: URL) {\n        self.installationDirectory = installationDirectory\n        self.shellClient = .live()\n    }\n\n    // MARK: - PackageManagerProtocol\n\n    func install(method installationMethod: InstallationMethod) throws -> [PackageManagerInstallStep] {\n        guard case .standardPackage(let source) = installationMethod else {\n            throw PackageManagerError.invalidConfiguration\n        }\n\n        let packagePath = installationDirectory.appending(path: source.entryName)\n        return [\n            initialize(in: packagePath),\n            runNpmInstall(source, installDir: packagePath),\n            verifyInstallation(source, installDir: packagePath)\n        ]\n\n    }\n\n    /// Checks if npm is installed\n    func isInstalled(method installationMethod: InstallationMethod) -> PackageManagerInstallStep {\n        PackageManagerInstallStep(\n            name: \"\",\n            confirmation: .required(\n                message: \"This package requires npm to install. Allow CodeEdit to run npm commands?\"\n            )\n        ) { model in\n            let versionOutput = try await model.runCommand(\"npm --version\")\n            let versionPattern = #\"^\\d+\\.\\d+\\.\\d+$\"#\n            let output = versionOutput.reduce(into: \"\") {\n                $0 += $1.trimmingCharacters(in: .whitespacesAndNewlines)\n            }\n            guard output.range(of: versionPattern, options: .regularExpression) != nil else {\n                throw PackageManagerError.packageManagerNotInstalled\n            }\n        }\n    }\n\n    /// Get the path to the binary\n    func getBinaryPath(for package: String) -> String {\n        let binDirectory = installationDirectory\n            .appending(path: package)\n            .appending(path: \"node_modules\")\n            .appending(path: \".bin\")\n        return binDirectory.appending(path: package).path\n    }\n\n    // MARK: - Initialize\n\n    /// Initializes the npm project if not already initialized\n    func initialize(in packagePath: URL) -> PackageManagerInstallStep {\n        PackageManagerInstallStep(name: \"Initialize Directory Structure\", confirmation: .none) { model in\n            // Clean existing files\n            let pkgJson = packagePath.appending(path: \"package.json\")\n            if FileManager.default.fileExists(atPath: pkgJson.path) {\n                try FileManager.default.removeItem(at: pkgJson)\n            }\n            let pkgLockJson = packagePath.appending(path: \"package-lock.json\")\n            if FileManager.default.fileExists(atPath: pkgLockJson.path) {\n                try FileManager.default.removeItem(at: pkgLockJson)\n            }\n\n            // Init npm directory with .npmrc file\n            try await model.createDirectoryStructure(for: packagePath)\n            _ = try await model.executeInDirectory(\n                in: packagePath.path, [\"npm init --yes --scope=codeedit\"]\n            )\n\n            let npmrcPath = packagePath.appending(path: \".npmrc\")\n            if !FileManager.default.fileExists(atPath: npmrcPath.path) {\n                try \"install-strategy=shallow\".write(to: npmrcPath, atomically: true, encoding: .utf8)\n            }\n        }\n    }\n\n    // MARK: - NPM Install\n\n    func runNpmInstall(_ source: PackageSource, installDir installationDirectory: URL) -> PackageManagerInstallStep {\n        let qualifiedSourceName = \"\\(source.pkgName)@\\(source.version)\"\n        let otherPackages = source.options[\"extraPackages\"]?\n            .split(separator: \",\")\n            .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } ?? []\n\n        var packageList = ([qualifiedSourceName] + otherPackages)\n\n        // FIXME: This will break with localization. Use real Foundation APIs for pluralizing lists.\n        let plural = packageList.count > 1\n        if plural, var last = packageList.last {\n            // Oxford comma\n            last = \"and \" + last\n            packageList[packageList.count - 1] = last\n        }\n        let packagesDescription = packageList.joined(separator: \", \")\n\n        let sSuffix = packageList.count > 1 ? \"s\" : \"\"\n        let suffix = plural ? \"these packages\" : \"this package\"\n\n        return PackageManagerInstallStep(\n            name: \"Install Package Using npm\",\n            confirmation: .required(\n                message: \"This requires the npm package\\(sSuffix) \\(packagesDescription).\"\n                + \"\\nAllow CodeEdit to install \\(suffix)?\"\n            )\n        ) { model in\n            do {\n                var installArgs = [\"npm\", \"install\", qualifiedSourceName]\n                if let dev = source.options[\"dev\"], dev.lowercased() == \"true\" {\n                    installArgs.append(\"--save-dev\")\n                }\n                for extraPackage in otherPackages {\n                    installArgs.append(extraPackage)\n                }\n\n                _ = try await model.executeInDirectory(\n                    in: installationDirectory.path(percentEncoded: false),\n                    installArgs\n                )\n            } catch {\n                let nodeModulesPath = installationDirectory.appending(path: \"node_modules\").path(percentEncoded: false)\n                try? FileManager.default.removeItem(atPath: nodeModulesPath)\n                throw error\n            }\n        }\n    }\n\n    // MARK: - Verify\n\n    /// Verify the installation was successful\n    private func verifyInstallation(\n        _ source: PackageSource,\n        installDir packagePath: URL\n    ) -> PackageManagerInstallStep {\n        let package = source.pkgName\n        let version = source.version\n\n        return PackageManagerInstallStep(\n            name: \"Verify Installation\",\n            confirmation: .none\n        ) { _ in\n            let packageJsonPath = packagePath.appending(path: \"package.json\").path\n\n            // Verify package.json contains the installed package\n            guard let packageJsonData = FileManager.default.contents(atPath: packageJsonPath),\n                  let packageJson = try? JSONSerialization.jsonObject(with: packageJsonData, options: []),\n                  let packageDict = packageJson as? [String: Any],\n                  let dependencies = packageDict[\"dependencies\"] as? [String: String],\n                  let installedVersion = dependencies[package] else {\n                throw PackageManagerError.installationFailed(\"Package not found in package.json\")\n            }\n\n            // Verify installed version matches requested version\n            let normalizedInstalledVersion = installedVersion.trimmingCharacters(in: CharacterSet(charactersIn: \"^~\"))\n            let normalizedRequestedVersion = version.trimmingCharacters(in: CharacterSet(charactersIn: \"^~\"))\n            if normalizedInstalledVersion != normalizedRequestedVersion &&\n                !installedVersion.contains(normalizedRequestedVersion) {\n                throw PackageManagerError.installationFailed(\n                    \"Version mismatch: Expected \\(version), but found \\(installedVersion)\"\n                )\n            }\n\n            // Verify the package exists in node_modules\n            let packageDirectory = packagePath\n                .appending(path: \"node_modules\")\n                .appending(path: package)\n            guard FileManager.default.fileExists(atPath: packageDirectory.path) else {\n                throw PackageManagerError.installationFailed(\"Package not found in node_modules\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/PackageManagers/Sources/PipPackageManager.swift",
    "content": "//\n//  PipPackageManager.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/3/25.\n//\n\nimport Foundation\n\nfinal class PipPackageManager: PackageManagerProtocol {\n    private let installationDirectory: URL\n\n    let shellClient: ShellClient\n\n    init(installationDirectory: URL) {\n        self.installationDirectory = installationDirectory\n        self.shellClient = .live()\n    }\n\n    // MARK: - PackageManagerProtocol\n\n    func install(method installationMethod: InstallationMethod) throws -> [PackageManagerInstallStep] {\n        guard case .standardPackage(let source) = installationMethod else {\n            throw PackageManagerError.invalidConfiguration\n        }\n\n        let packagePath = installationDirectory.appending(path: source.entryName)\n        return [\n            initialize(in: packagePath),\n            runPipInstall(source, in: packagePath),\n            updateRequirements(in: packagePath),\n            verifyInstallation(source, in: packagePath)\n        ]\n    }\n\n    func isInstalled(method installationMethod: InstallationMethod) -> PackageManagerInstallStep {\n        PackageManagerInstallStep(name: \"\", confirmation: .none) { model in\n            let pipCommands = [\"pip3 --version\", \"python3 -m pip --version\"]\n            var didFindPip = false\n            for command in pipCommands {\n                do {\n                    let versionOutput = try await model.runCommand(command)\n                    let versionPattern = #\"pip \\d+\\.\\d+\"#\n                    let output = versionOutput.reduce(into: \"\") {\n                        $0 += $1.trimmingCharacters(in: .whitespacesAndNewlines)\n                    }\n                    if output.range(of: versionPattern, options: .regularExpression) != nil {\n                        didFindPip = true\n                        break\n                    }\n                } catch {\n                    continue\n                }\n            }\n            guard didFindPip else {\n                throw PackageManagerError.packageManagerNotInstalled\n            }\n        }\n\n    }\n\n    /// Get the binary path for a Python package\n    func getBinaryPath(for package: String) -> String {\n        let packagePath = installationDirectory.appending(path: package)\n        let customBinPath = packagePath.appending(path: \"bin\").appending(path: package).path\n        if FileManager.default.fileExists(atPath: customBinPath) {\n            return customBinPath\n        }\n        return packagePath.appending(path: \"venv\").appending(path: \"bin\").appending(path: package).path\n    }\n\n    // MARK: - Initialize\n\n    func initialize(in packagePath: URL) -> PackageManagerInstallStep {\n        PackageManagerInstallStep(name: \"Initialize Directory Structure\", confirmation: .none) { model in\n            try await model.createDirectoryStructure(for: packagePath)\n            try await model.executeInDirectory(in: packagePath.path(percentEncoded: false), [\"python -m venv venv\"])\n\n            let requirementsPath = packagePath.appending(path: \"requirements.txt\")\n            if !FileManager.default.fileExists(atPath: requirementsPath.path) {\n                try \"# Package requirements\\n\".write(to: requirementsPath, atomically: true, encoding: .utf8)\n            }\n        }\n    }\n\n    // MARK: - Pip Install\n\n    func runPipInstall(_ source: PackageSource, in packagePath: URL) -> PackageManagerInstallStep {\n        let pipCommand = getPipCommand(in: packagePath)\n        return PackageManagerInstallStep(\n            name: \"Install Package Using pip\",\n            confirmation: .required(\n                message: \"This requires the pip package \\(source.pkgName).\"\n                + \"\\nAllow CodeEdit to install this package?\"\n            )\n        ) { model in\n            var installArgs = [pipCommand, \"install\"]\n\n            if source.version.lowercased() != \"latest\" {\n                installArgs.append(\"\\(source.pkgName)==\\(source.version)\")\n            } else {\n                installArgs.append(source.pkgName)\n            }\n\n            let extras = source.options[\"extra\"]\n            if let extras {\n                if let lastIndex = installArgs.indices.last {\n                    installArgs[lastIndex] += \"[\\(extras)]\"\n                }\n            }\n\n            try await model.executeInDirectory(in: packagePath.path, installArgs)\n        }\n    }\n\n    // MARK: - Update Requirements.txt\n\n    /// Update the requirements.txt file with the installed package and extras\n    private func updateRequirements(in packagePath: URL) -> PackageManagerInstallStep {\n        let pipCommand = getPipCommand(in: packagePath)\n        return PackageManagerInstallStep(\n            name: \"Update requirements.txt\",\n            confirmation: .none\n        ) { model in\n            let requirementsPath = packagePath.appending(path: \"requirements.txt\")\n\n            let freezeOutput = try await model.executeInDirectory(\n                in: packagePath.path(percentEncoded: false),\n                [\"\\(pipCommand)\", \"freeze\"]\n            )\n\n            await model.status(\"Writing requirements to requirements.txt\")\n            let requirementsContent = freezeOutput.joined(separator: \"\\n\") + \"\\n\"\n            try requirementsContent.write(to: requirementsPath, atomically: true, encoding: .utf8)\n        }\n    }\n\n    // MARK: - Verify Installation\n\n    private func verifyInstallation(_ source: PackageSource, in packagePath: URL) -> PackageManagerInstallStep {\n        let pipCommand = getPipCommand(in: packagePath)\n        return PackageManagerInstallStep(\n            name: \"Verify Installation\",\n            confirmation: .none\n        ) { model in\n            let output = try await model.executeInDirectory(\n                in: packagePath.path(percentEncoded: false),\n                [\"\\(pipCommand)\", \"list\", \"--format=freeze\"]\n            )\n\n            // Normalize package names for comparison\n            let normalizedPackageHyphen = source.pkgName.replacingOccurrences(of: \"_\", with: \"-\").lowercased()\n            let normalizedPackageUnderscore = source.pkgName.replacingOccurrences(of: \"-\", with: \"_\").lowercased()\n\n            // Check if the package name appears in requirements.txt\n            let installedPackages = output.map { line in\n                line.lowercased().split(separator: \"=\").first?.trimmingCharacters(in: .whitespacesAndNewlines)\n            }\n            let packageFound = installedPackages.contains { installedPackage in\n                installedPackage == normalizedPackageHyphen || installedPackage == normalizedPackageUnderscore\n            }\n\n            guard packageFound else {\n                throw PackageManagerError.installationFailed(\"Package \\(source.pkgName) not found in pip list\")\n            }\n        }\n    }\n\n    private func getPipCommand(in packagePath: URL) -> String {\n        let venvPip = \"venv/bin/pip\"\n        return FileManager.default.fileExists(atPath: packagePath.appending(path: venvPip).path)\n        ? venvPip\n        : \"python3 -m pip\"\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/PackageSourceParser/PackageSourceParser+Cargo.swift",
    "content": "//\n//  PackageSourceParser+Cargo.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 3/12/25.\n//\n\nextension PackageSourceParser {\n    static func parseCargoPackage(_ entry: RegistryItem) -> InstallationMethod {\n        // Format: pkg:cargo/PACKAGE@VERSION?PARAMS\n        let pkgPrefix = \"pkg:cargo/\"\n        let sourceId = entry.source.id.removingPercentEncoding ?? entry.source.id\n        guard sourceId.hasPrefix(pkgPrefix) else { return .unknown }\n\n        let pkgString = sourceId.dropFirst(pkgPrefix.count)\n\n        let components = pkgString.split(separator: \"?\", maxSplits: 1)\n        let packageVersion = String(components[0])\n        let parameters = components.count > 1 ? String(components[1]) : \"\"\n\n        let packageVersionParts = packageVersion.split(separator: \"@\", maxSplits: 1)\n        guard packageVersionParts.count >= 1 else { return .unknown }\n\n        let packageName = String(packageVersionParts[0])\n        let version = packageVersionParts.count > 1 ? String(packageVersionParts[1]) : \"latest\"\n\n        // Parse parameters as options\n        var options: [String: String] = [\"buildTool\": \"cargo\"]\n        var repositoryUrl: String?\n        var gitReference: PackageSource.GitReference?\n\n        let paramPairs = parameters.split(separator: \"&\")\n        for pair in paramPairs {\n            let keyValue = pair.split(separator: \"=\", maxSplits: 1)\n            guard keyValue.count == 2 else { continue }\n\n            let key = String(keyValue[0])\n            let value = String(keyValue[1])\n\n            if key == \"repository_url\" {\n                repositoryUrl = value\n            } else if key == \"rev\" && value.lowercased() == \"true\" {\n                gitReference = .revision(version)\n            } else if key == \"tag\" && value.lowercased() == \"true\" {\n                gitReference = .tag(version)\n            } else {\n                options[key] = value\n            }\n        }\n\n        // If we have a repository URL but no git reference specified,\n        // default to tag for versions and revision for commit hashes\n        if repositoryUrl != nil, gitReference == nil {\n            if version.range(of: \"^[0-9a-f]{40}$\", options: .regularExpression) != nil {\n                gitReference = .revision(version)\n            } else {\n                gitReference = .tag(version)\n            }\n        }\n\n        let source = PackageSource(\n            sourceId: sourceId,\n            type: .cargo,\n            pkgName: packageName,\n            entryName: entry.name,\n            version: version,\n            repositoryUrl: repositoryUrl,\n            gitReference: gitReference,\n            options: options\n        )\n        return .standardPackage(source: source)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/PackageSourceParser/PackageSourceParser+Gem.swift",
    "content": "//\n//  PackageSourceParser+Gem.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 3/12/25.\n//\n\nextension PackageSourceParser {\n    static func parseRubyGem(_ entry: RegistryItem) -> InstallationMethod {\n        // Format: pkg:gem/PACKAGE@VERSION?PARAMS\n        let pkgPrefix = \"pkg:gem/\"\n        let sourceId = entry.source.id.removingPercentEncoding ?? entry.source.id\n        guard sourceId.hasPrefix(pkgPrefix) else { return .unknown }\n\n        let pkgString = sourceId.dropFirst(pkgPrefix.count)\n\n        let components = pkgString.split(separator: \"?\", maxSplits: 1)\n        let packageVersion = String(components[0])\n        let parameters = components.count > 1 ? String(components[1]) : \"\"\n\n        let packageVersionParts = packageVersion.split(separator: \"@\", maxSplits: 1)\n        guard packageVersionParts.count >= 1 else { return .unknown }\n\n        let packageName = String(packageVersionParts[0])\n        let version = packageVersionParts.count > 1 ? String(packageVersionParts[1]) : \"latest\"\n\n        // Parse parameters as options\n        var options: [String: String] = [\"buildTool\": \"gem\"]\n        var repositoryUrl: String?\n        var gitReference: PackageSource.GitReference?\n\n        let paramPairs = parameters.split(separator: \"&\")\n        for pair in paramPairs {\n            let keyValue = pair.split(separator: \"=\", maxSplits: 1)\n            guard keyValue.count == 2 else { continue }\n\n            let key = String(keyValue[0])\n            let value = String(keyValue[1])\n\n            if key == \"repository_url\" {\n                repositoryUrl = value\n            } else if key == \"rev\" && value.lowercased() == \"true\" {\n                gitReference = .revision(version)\n            } else if key == \"tag\" && value.lowercased() == \"true\" {\n                gitReference = .tag(version)\n            } else {\n                options[key] = value\n            }\n        }\n\n        let source = PackageSource(\n            sourceId: sourceId,\n            type: .gem,\n            pkgName: packageName,\n            entryName: entry.name,\n            version: version,\n            repositoryUrl: repositoryUrl,\n            gitReference: gitReference,\n            options: options\n        )\n        return .standardPackage(source: source)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/PackageSourceParser/PackageSourceParser+Golang.swift",
    "content": "//\n//  PackageSourceParser+Golang.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 3/12/25.\n//\n\nextension PackageSourceParser {\n    static func parseGolangPackage(_ entry: RegistryItem) -> InstallationMethod {\n        // Format: pkg:golang/PACKAGE@VERSION#SUBPATH?PARAMS\n        let pkgPrefix = \"pkg:golang/\"\n        let sourceId = entry.source.id.removingPercentEncoding ?? entry.source.id\n        guard sourceId.hasPrefix(pkgPrefix) else { return .unknown }\n\n        let pkgString = sourceId.dropFirst(pkgPrefix.count)\n\n        // Extract subpath first if present\n        let subpathComponents = pkgString.split(separator: \"#\", maxSplits: 1)\n        let packageVersionParam = String(subpathComponents[0])\n        let subpath = subpathComponents.count > 1 ? String(subpathComponents[1]) : nil\n\n        // Then split into package@version and parameters\n        let components = packageVersionParam.split(separator: \"?\", maxSplits: 1)\n        let packageVersion = String(components[0])\n        let parameters = components.count > 1 ? String(components[1]) : \"\"\n\n        let packageVersionParts = packageVersion.split(separator: \"@\", maxSplits: 1)\n        guard packageVersionParts.count >= 1 else { return .unknown }\n\n        let packageName = String(packageVersionParts[0])\n        let version = packageVersionParts.count > 1 ? String(packageVersionParts[1]) : \"latest\"\n\n        // Parse parameters as options\n        var options: [String: String] = [\"buildTool\": \"golang\"]\n        options[\"subpath\"] = subpath\n        var repositoryUrl: String?\n        var gitReference: PackageSource.GitReference?\n\n        let paramPairs = parameters.split(separator: \"&\")\n        for pair in paramPairs {\n            let keyValue = pair.split(separator: \"=\", maxSplits: 1)\n            guard keyValue.count == 2 else { continue }\n\n            let key = String(keyValue[0])\n            let value = String(keyValue[1])\n\n            if key == \"repository_url\" {\n                repositoryUrl = value\n            } else if key == \"rev\" && value.lowercased() == \"true\" {\n                gitReference = .revision(version)\n            } else if key == \"tag\" && value.lowercased() == \"true\" {\n                gitReference = .tag(version)\n            } else {\n                options[key] = value\n            }\n        }\n\n        // For Go packages, the package name is often also the repository URL\n        if repositoryUrl == nil {\n            repositoryUrl = \"https://\\(packageName)\"\n        }\n\n        let source = PackageSource(\n            sourceId: sourceId,\n            type: .golang,\n            pkgName: packageName,\n            entryName: entry.name,\n            version: version,\n            repositoryUrl: repositoryUrl,\n            gitReference: gitReference,\n            options: options\n        )\n        return .standardPackage(source: source)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/PackageSourceParser/PackageSourceParser+NPM.swift",
    "content": "//\n//  PackageSourceParser+NPM.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 3/12/25.\n//\n\nextension PackageSourceParser {\n    static func parseNpmPackage(_ entry: RegistryItem) -> InstallationMethod {\n        // Format: pkg:npm/PACKAGE@VERSION?PARAMS\n        let pkgPrefix = \"pkg:npm/\"\n        let sourceId = entry.source.id.removingPercentEncoding ?? entry.source.id\n        guard sourceId.hasPrefix(pkgPrefix) else { return .unknown }\n\n        let pkgString = sourceId.dropFirst(pkgPrefix.count)\n\n        // Split into package@version and parameters\n        let components = pkgString.split(separator: \"?\", maxSplits: 1)\n        let packageVersion = String(components[0])\n        let parameters = components.count > 1 ? String(components[1]) : \"\"\n\n        let (packageName, version) = parseNPMPackageNameAndVersion(packageVersion)\n\n        // Parse parameters as options\n        var options: [String: String] = [\"buildTool\": \"npm\"]\n        var repositoryUrl: String?\n        var gitReference: PackageSource.GitReference?\n\n        let paramPairs = parameters.split(separator: \"&\")\n        for pair in paramPairs {\n            let keyValue = pair.split(separator: \"=\", maxSplits: 1)\n            guard keyValue.count == 2 else { continue }\n\n            let key = String(keyValue[0])\n            let value = String(keyValue[1])\n\n            if key == \"repository_url\" {\n                repositoryUrl = value\n            } else if key == \"rev\" && value.lowercased() == \"true\" {\n                gitReference = .revision(version)\n            } else if key == \"tag\" && value.lowercased() == \"true\" {\n                gitReference = .tag(version)\n            } else {\n                options[key] = value\n            }\n        }\n\n        let source = PackageSource(\n            sourceId: sourceId,\n            type: .npm,\n            pkgName: packageName,\n            entryName: entry.name,\n            version: version,\n            repositoryUrl: repositoryUrl,\n            gitReference: gitReference,\n            options: options\n        )\n        return .standardPackage(source: source)\n    }\n\n    private static func parseNPMPackageNameAndVersion(_ packageVersion: String) -> (String, String) {\n        var packageName: String\n        var version: String = \"latest\"\n\n        if packageVersion.contains(\"@\") && !packageVersion.hasPrefix(\"@\") {\n            // Regular package with version: package@1.0.0\n            let parts = packageVersion.split(separator: \"@\", maxSplits: 1)\n            packageName = String(parts[0])\n            if parts.count > 1 {\n                version = String(parts[1])\n            }\n        } else if packageVersion.hasPrefix(\"@\") {\n            // Scoped package: @org/package@1.0.0\n            if let atIndex = packageVersion[\n                packageVersion.index(after: packageVersion.startIndex)...\n            ].firstIndex(of: \"@\") {\n                packageName = String(packageVersion[..<atIndex])\n                version = String(packageVersion[packageVersion.index(after: atIndex)...])\n            } else {\n                packageName = packageVersion\n            }\n        } else {\n            packageName = packageVersion\n        }\n\n        return (packageName, version)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/PackageSourceParser/PackageSourceParser+PYPI.swift",
    "content": "//\n//  PackageSourceParser+PYPI.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 3/12/25.\n//\n\nextension PackageSourceParser {\n    static func parsePythonPackage(_ entry: RegistryItem) -> InstallationMethod {\n        // Format: pkg:pypi/PACKAGE@VERSION?PARAMS\n        let pkgPrefix = \"pkg:pypi/\"\n        let sourceId = entry.source.id\n        guard sourceId.hasPrefix(pkgPrefix) else { return .unknown }\n\n        let pkgString = sourceId.dropFirst(pkgPrefix.count)\n\n        let components = pkgString.split(separator: \"?\", maxSplits: 1)\n        let packageVersion = String(components[0])\n        let parameters = components.count > 1 ? String(components[1]) : \"\"\n\n        let packageVersionParts = packageVersion.split(separator: \"@\", maxSplits: 1)\n        guard packageVersionParts.count >= 1 else { return .unknown }\n\n        let packageName = String(packageVersionParts[0])\n        let version = packageVersionParts.count > 1 ? String(packageVersionParts[1]) : \"latest\"\n\n        // Parse parameters as options\n        var options: [String: String] = [\"buildTool\": \"pip\"]\n        var repositoryUrl: String?\n        var gitReference: PackageSource.GitReference?\n\n        let paramPairs = parameters.split(separator: \"&\")\n        for pair in paramPairs {\n            let keyValue = pair.split(separator: \"=\", maxSplits: 1)\n            guard keyValue.count == 2 else { continue }\n\n            let key = String(keyValue[0])\n            let value = String(keyValue[1])\n\n            if key == \"repository_url\" {\n                repositoryUrl = value\n            } else if key == \"rev\" && value.lowercased() == \"true\" {\n                gitReference = .revision(version)\n            } else if key == \"tag\" && value.lowercased() == \"true\" {\n                gitReference = .tag(version)\n            } else {\n                options[key] = value\n            }\n        }\n\n        let source = PackageSource(\n            sourceId: sourceId,\n            type: .pip,\n            pkgName: packageName,\n            entryName: entry.name,\n            version: version,\n            repositoryUrl: repositoryUrl,\n            gitReference: gitReference,\n            options: options\n        )\n        return .standardPackage(source: source)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/PackageSourceParser/PackageSourceParser.swift",
    "content": "//\n//  PackageSourceParser.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/3/25.\n//\n\nimport Foundation\n\n/// Parser for package source IDs\nenum PackageSourceParser {\n    static func parseGithubPackage(_ entry: RegistryItem) -> InstallationMethod {\n        // Format: pkg:github/OWNER/REPO@COMMIT_HASH\n        let pkgPrefix = \"pkg:github/\"\n        let sourceId = entry.source.id\n        guard sourceId.hasPrefix(pkgPrefix) else { return .unknown }\n\n        let pkgString = sourceId.dropFirst(pkgPrefix.count)\n        let packagePathVersion = pkgString.split(separator: \"@\", maxSplits: 1)\n        guard packagePathVersion.count >= 1 else { return .unknown }\n\n        let packagePath = String(packagePathVersion[0])\n        let version = packagePathVersion.count > 1 ? String(packagePathVersion[1]) : \"main\"\n\n        let pathComponents = packagePath.split(separator: \"/\")\n        guard pathComponents.count >= 2 else { return .unknown }\n\n        let owner = String(pathComponents[0])\n        let repo = String(pathComponents[1])\n        let packageName = repo\n        let repositoryUrl = \"https://github.com/\\(owner)/\\(repo)\"\n\n        let isCommitHash = version.range(of: \"^[0-9a-f]{40}$\", options: .regularExpression) != nil\n        let gitReference: PackageSource.GitReference = isCommitHash ? .revision(version) : .tag(version)\n\n        // Is this going to be built from source or downloaded\n        let isSourceBuild = if entry.source.asset == nil {\n            true\n        } else {\n            false\n        }\n\n        let source = PackageSource(\n            sourceId: sourceId,\n            type: isSourceBuild ? .sourceBuild : .github,\n            pkgName: packageName,\n            entryName: entry.name,\n            version: version,\n            repositoryUrl: repositoryUrl,\n            gitReference: gitReference,\n            options: [:]\n        )\n        if isSourceBuild {\n            return parseGithubSourceBuild(source, entry)\n        } else {\n            return parseGithubBinaryDownload(source, entry)\n        }\n    }\n\n    private static func parseGithubBinaryDownload(\n        _ pkgSource: PackageSource,\n        _ entry: RegistryItem\n    ) -> InstallationMethod {\n        guard let assetContainer = entry.source.asset,\n              let repoURL = pkgSource.repositoryUrl,\n              case .tag(let gitTag) = pkgSource.gitReference,\n              var fileName = assetContainer.getDarwinFileName(),\n              !fileName.isEmpty\n        else {\n            return .unknown\n        }\n\n        do {\n            var registryInfo = try entry.toDictionary()\n            registryInfo[\"version\"] = pkgSource.version\n            fileName = try RegistryItemTemplateParser.process(\n                template: fileName, with: registryInfo\n            )\n        } catch {\n            return .unknown\n        }\n\n        let downloadURL = URL(string: \"\\(repoURL)/releases/download/\\(gitTag)/\\(fileName)\")!\n        return .binaryDownload(source: pkgSource, url: downloadURL)\n    }\n\n    private static func parseGithubSourceBuild(\n        _ pkgSource: PackageSource,\n        _ entry: RegistryItem\n    ) -> InstallationMethod {\n        guard let build = entry.source.build,\n              let command = build.getUnixBuildCommand()\n        else {\n            return .unknown\n        }\n        return .sourceBuild(source: pkgSource, command: command)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/RegistryItemTemplateParser.swift",
    "content": "//\n//  RegistryItemTemplateParser.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 3/9/25.\n//\n\nimport Foundation\n\n/// This parser is used to parse expressions that may be included in a field of a registry item.\n///\n/// Example:\n/// \"protolint_{{ version | strip_prefix \\\"v\\\" }}_darwin_arm64.tar.gz\" will be parsed into:\n/// protolint_0.53.0_darwin_arm64.tar.gz\nenum RegistryItemTemplateParser {\n\n    enum TemplateError: Error {\n        case invalidFilter(String)\n        case missingVariable(String)\n        case invalidPath(String)\n        case missingKey(String)\n    }\n\n    private enum Filter {\n        case stripPrefix(String)\n\n        static func parse(_ filterString: String) throws -> Filter {\n            let components = filterString.trimmingCharacters(in: .whitespaces).components(separatedBy: \" \")\n            if components.count >= 2 && components[0] == \"strip_prefix\" {\n                // Extract the quoted string value\n                let prefixRaw = components[1]\n                if prefixRaw.hasPrefix(\"\\\"\") && prefixRaw.hasSuffix(\"\\\"\") {\n                    let prefix = String(prefixRaw.dropFirst().dropLast())\n                    return .stripPrefix(prefix)\n                }\n            }\n            throw TemplateError.invalidFilter(filterString)\n        }\n\n        func apply(to value: String) -> String {\n            switch self {\n            case .stripPrefix(let prefix):\n                if value.hasPrefix(prefix) {\n                    return String(value.dropFirst(prefix.count))\n                }\n                return value\n            }\n        }\n    }\n\n    static func process(template: String, with context: [String: Any]) throws -> String {\n        var result = template\n\n        // Find all {{ ... }} patterns\n        let pattern = \"\\\\{\\\\{([^\\\\}]+)\\\\}\\\\}\"\n        let regex = try NSRegularExpression(pattern: pattern, options: [])\n        let matches = regex.matches(\n            in: template,\n            options: [],\n            range: NSRange(location: 0, length: template.utf16.count)\n        )\n\n        // Process matches in reverse order to not invalidate ranges\n        for match in matches.reversed() {\n            guard Range(match.range, in: template) != nil else { continue }\n\n            // Extract the content between {{ and }}\n            let expressionRange = Range(match.range(at: 1), in: template)!\n            let expression = String(template[expressionRange])\n\n            // Split by pipe to separate variable path from filters\n            let components = expression.components(separatedBy: \"|\").filter { !$0.isEmpty }\n            let pathExpression = components[0].trimmingCharacters(in: .whitespaces)\n            let value = try getValueFromPath(pathExpression, in: context)\n\n            // Apply filters\n            var processedValue = value\n            if components.count > 1 {\n                for item in 1..<components.count {\n                    let filterString = components[item].trimmingCharacters(in: .whitespaces)\n                    let filter = try Filter.parse(filterString)\n                    processedValue = filter.apply(to: processedValue)\n                }\n            }\n\n            // Replace in result\n            if let matchRange = Range(match.range, in: result) {\n                result = result.replacingCharacters(in: matchRange, with: processedValue)\n            }\n        }\n\n        return result\n    }\n\n    /// Get a value by traversing a dot-separated path in a nested dictionary\n    private static func getValueFromPath(_ path: String, in context: [String: Any]) throws -> String {\n        let pathComponents = path.components(separatedBy: \".\")\n        var currentValue: Any = context\n\n        for component in pathComponents {\n            if let dict = currentValue as? [String: Any] {\n                if let value = dict[component] {\n                    currentValue = value\n                } else {\n                    throw TemplateError.missingKey(component)\n                }\n            } else if let array = currentValue as? [Any], let index = Int(component) {\n                if index >= 0 && index < array.count {\n                    currentValue = array[index]\n                } else {\n                    throw TemplateError.invalidPath(\"Array index out of bounds: \\(component)\")\n                }\n            } else {\n                throw TemplateError.invalidPath(\"Cannot access component: \\(component)\")\n            }\n        }\n\n        // Convert the final value to a string\n        if let stringValue = currentValue as? String {\n            return stringValue\n        } else if let intValue = currentValue as? Int {\n            return String(intValue)\n        } else if let doubleValue = currentValue as? Double {\n            return String(doubleValue)\n        } else if let boolValue = currentValue as? Bool {\n            return String(boolValue)\n        } else if currentValue is [Any] || currentValue is [String: Any] {\n            throw TemplateError.invalidPath(\"Path resolves to a complex object, not a simple value\")\n        } else {\n            return String(describing: currentValue)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/RegistryManager+HandleRegistryFile.swift",
    "content": "//\n//  RegistryManager+HandleRegistryFile.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 3/14/25.\n//\n\nimport Foundation\n\nextension RegistryManager {\n    /// Downloads the latest registry\n    func downloadRegistryItems() async {\n        isDownloadingRegistry = true\n        defer { isDownloadingRegistry = false }\n\n        let registryData, checksumData: Data\n        do {\n            async let zipDataTask = download(from: self.registryURL)\n            async let checksumsTask = download(from: self.checksumURL)\n\n            (registryData, checksumData) = try await (zipDataTask, checksumsTask)\n        } catch {\n            handleUpdateError(error)\n            return\n        }\n\n        do {\n            // Make sure the extensions folder exists first\n            try FileManager.default.createDirectory(at: installPath, withIntermediateDirectories: true)\n\n            let tempZipURL = installPath.appending(path: \"temp.zip\")\n            let checksumDestination = installPath.appending(path: \"checksums.txt\")\n\n            // Delete existing zip data if it exists\n            if FileManager.default.fileExists(atPath: tempZipURL.path) {\n                try FileManager.default.removeItem(at: tempZipURL)\n            }\n            let registryJsonPath = installPath.appending(path: \"registry.json\").path\n            if FileManager.default.fileExists(atPath: registryJsonPath) {\n                try FileManager.default.removeItem(atPath: registryJsonPath)\n            }\n\n            // Write the zip data to a temporary file, then unzip\n            try registryData.write(to: tempZipURL)\n            try FileManager.default.unzipItem(at: tempZipURL, to: installPath)\n            try FileManager.default.removeItem(at: tempZipURL)\n\n            try checksumData.write(to: checksumDestination)\n            downloadError = nil\n        } catch {\n            handleUpdateError(RegistryManagerError.writeFailed(error: error))\n            return\n        }\n\n        do {\n            if let items = loadItemsFromDisk() {\n                setRegistryItems(items)\n            } else {\n                throw RegistryManagerError.failedToSaveRegistryCache\n            }\n        } catch {\n            handleUpdateError(error)\n        }\n    }\n\n    func handleUpdateError(_ error: Error) {\n        self.downloadError = error\n        if let regError = error as? RegistryManagerError {\n            switch regError {\n            case .installationRunning:\n                return // Shouldn't need to handle\n            case .invalidResponse(let statusCode):\n                logger.error(\"Invalid response received: \\(statusCode)\")\n            case let .downloadFailed(url, error):\n                logger.error(\"Download failed for \\(url.absoluteString): \\(error.localizedDescription)\")\n            case let .maxRetriesExceeded(url, error):\n                logger.error(\"Max retries exceeded for \\(url.absoluteString): \\(error.localizedDescription)\")\n            case let .writeFailed(error):\n                logger.error(\"Failed to write files to disk: \\(error.localizedDescription)\")\n            case .failedToSaveRegistryCache:\n                logger.error(\"Failed to read registry from cache after download and write.\")\n            }\n        } else {\n            logger.error(\"Unexpected registry error: \\(error.localizedDescription)\")\n        }\n    }\n\n    /// Attempts downloading from `url`, with error handling and a retry policy\n    @Sendable\n    func download(from url: URL, attempt: Int = 1) async throws -> Data {\n        do {\n            let (data, response) = try await URLSession.shared.data(from: url)\n\n            guard let httpResponse = response as? HTTPURLResponse else {\n                throw RegistryManagerError.downloadFailed(\n                    url: url, error: NSError(domain: \"Invalid response type\", code: -1)\n                )\n            }\n            guard (200...299).contains(httpResponse.statusCode) else {\n                throw RegistryManagerError.invalidResponse(statusCode: httpResponse.statusCode)\n            }\n\n            return data\n        } catch {\n            if attempt <= 3 {\n                let delay = pow(2.0, Double(attempt))\n                try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))\n                return try await download(from: url, attempt: attempt + 1)\n            } else {\n                throw RegistryManagerError.maxRetriesExceeded(url: url, lastError: error)\n            }\n        }\n    }\n\n    /// Loads registry items from disk\n    func loadItemsFromDisk() -> [RegistryItem]? {\n        let registryPath = installPath.appending(path: \"registry.json\")\n        let fileManager = FileManager.default\n\n        // Update the file every 24 hours\n        let needsUpdate = !fileManager.fileExists(atPath: registryPath.path) || {\n            guard let attributes = try? fileManager.attributesOfItem(atPath: registryPath.path),\n                  let modificationDate = attributes[.modificationDate] as? Date else {\n                return true\n            }\n            let hoursSinceLastUpdate = Date().timeIntervalSince(modificationDate) / 3600\n            return hoursSinceLastUpdate >= 24\n        }()\n\n        if needsUpdate {\n            return nil\n        }\n\n        do {\n            let registryData = try Data(contentsOf: registryPath)\n            let items = try JSONDecoder().decode([RegistryItem].self, from: registryData)\n            return items.filter { $0.categories.contains(\"LSP\") }\n        } catch {\n            return nil\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Registry/RegistryManager.swift",
    "content": "//\n//  Registry.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 1/29/25.\n//\n\nimport OSLog\nimport Foundation\nimport ZIPFoundation\nimport Combine\n\n@MainActor\nfinal class RegistryManager: ObservableObject {\n    static let shared = RegistryManager()\n\n    let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? \"\", category: \"RegistryManager\")\n    let installPath = Settings.shared.baseURL.appending(path: \"Language Servers\")\n\n    /// The URL of where the registry.json file will be downloaded from\n    let registryURL = URL(\n        string: \"https://github.com/mason-org/mason-registry/releases/latest/download/registry.json.zip\"\n    )!\n    /// The URL of where the checksums.txt file will be downloaded from\n    let checksumURL = URL(\n        string: \"https://github.com/mason-org/mason-registry/releases/latest/download/checksums.txt\"\n    )!\n\n    @Published var isDownloadingRegistry: Bool = false\n    /// Holds an errors found while downloading the registry file. Needs a UI to dismiss, is logged.\n    @Published var downloadError: Error?\n    /// Any currently running installation operation.\n    @Published var runningInstall: PackageManagerInstallOperation?\n    private var installTask: Task<Void, Never>?\n\n    /// Indicates if the manager is currently installing a package.\n    var isInstalling: Bool {\n        installTask != nil\n    }\n\n    /// Reference to cached registry data. Will be removed from memory after a certain amount of time.\n    private var cachedRegistry: CachedRegistry?\n    /// Timer to clear expired cache\n    private var cleanupTimer: Timer?\n    /// Public access to registry items with cache management\n    @Published public private(set) var registryItems: [RegistryItem] = []\n\n    @AppSettings(\\.languageServers.installedLanguageServers)\n    var installedLanguageServers: [String: SettingsData.InstalledLanguageServer]\n\n    init() {\n        // Load the registry items from disk again after cache expires\n        if let items = loadItemsFromDisk() {\n            setRegistryItems(items)\n        } else {\n            Task {\n                await downloadRegistryItems()\n            }\n        }\n    }\n\n    deinit {\n        cleanupTimer?.invalidate()\n    }\n\n    // MARK: - Enable/Disable\n\n    func setPackageEnabled(packageName: String, enabled: Bool) {\n        installedLanguageServers[packageName]?.isEnabled = enabled\n    }\n\n    // MARK: - Uninstall\n\n    @MainActor\n    func removeLanguageServer(packageName: String) async throws {\n        let packageName = packageName.removingPercentEncoding ?? packageName\n        let packageDirectory = installPath.appending(path: packageName)\n\n        guard FileManager.default.fileExists(atPath: packageDirectory.path) else {\n            installedLanguageServers.removeValue(forKey: packageName)\n            return\n        }\n\n        // Add to activity viewer\n        NotificationCenter.default.post(\n            name: .taskNotification,\n            object: nil,\n            userInfo: [\n                \"id\": packageName,\n                \"action\": \"create\",\n                \"title\": \"Removing \\(packageName)\"\n            ]\n        )\n\n        do {\n            try await Task.detached(priority: .userInitiated) {\n                try FileManager.default.removeItem(at: packageDirectory)\n            }.value\n            installedLanguageServers.removeValue(forKey: packageName)\n        } catch {\n            throw error\n        }\n    }\n\n    // MARK: - Install\n\n    public func installOperation(package: RegistryItem) throws -> PackageManagerInstallOperation {\n        guard !isInstalling else {\n            throw RegistryManagerError.installationRunning\n        }\n        guard let method = package.installMethod,\n              let manager = method.packageManager(installPath: installPath) else {\n            throw PackageManagerError.invalidConfiguration\n        }\n        let installSteps = try manager.install(method: method)\n        return PackageManagerInstallOperation(package: package, steps: installSteps)\n    }\n\n    /// Starts the actual installation process for a package\n    public func startInstallation(operation installOperation: PackageManagerInstallOperation) throws {\n        guard !isInstalling else {\n            throw RegistryManagerError.installationRunning\n        }\n\n        guard let method = installOperation.package.installMethod else {\n            throw PackageManagerError.invalidConfiguration\n        }\n\n        // Run it!\n        installPackage(operation: installOperation, method: method)\n    }\n\n    private func installPackage(operation: PackageManagerInstallOperation, method: InstallationMethod) {\n        installTask = Task { [weak self] in\n            defer {\n                self?.installTask = nil\n                self?.runningInstall = nil\n            }\n            self?.runningInstall = operation\n\n            // Add to activity viewer\n            let activityTitle = \"\\(operation.package.name)\\(\"@\" + (method.version ?? \"latest\"))\"\n            TaskNotificationHandler.postTask(\n                action: .create,\n                model: TaskNotificationModel(id: operation.package.name, title: \"Installing \\(activityTitle)\")\n            )\n\n            guard !Task.isCancelled else { return }\n\n            do {\n                try await operation.run()\n            } catch {\n                self?.updateActivityViewer(operation.package.name, activityTitle, fail: true)\n                return\n            }\n\n            self?.installedLanguageServers[operation.package.name] = .init(\n                packageName: operation.package.name,\n                isEnabled: true,\n                version: method.version ?? \"\"\n            )\n            self?.updateActivityViewer(operation.package.name, activityTitle, fail: false)\n        }\n    }\n\n    // MARK: - Cancel Install\n\n    /// Cancel the currently running installation\n    public func cancelInstallation() {\n        runningInstall?.cancel()\n        installTask?.cancel()\n        installTask = nil\n    }\n\n    /// Updates the activity viewer with the status of the language server installation\n    @MainActor\n    private func updateActivityViewer(\n        _ id: String,\n        _ activityName: String,\n        fail failed: Bool\n    ) {\n        if failed {\n            NotificationManager.shared.post(\n                iconSymbol: \"xmark.circle\",\n                iconColor: .clear,\n                title: \"Could not install \\(activityName)\",\n                description: \"There was a problem during installation.\",\n                actionButtonTitle: \"Done\",\n                action: {},\n            )\n        } else {\n            TaskNotificationHandler.postTask(\n                action: .update,\n                model: TaskNotificationModel(id: id, title: \"Successfully installed \\(activityName)\", isLoading: false)\n            )\n            NotificationCenter.default.post(\n                name: .taskNotification,\n                object: nil,\n                userInfo: [\n                    \"id\": id,\n                    \"action\": \"deleteWithDelay\",\n                    \"delay\": 5.0,\n                ]\n            )\n        }\n    }\n\n    // MARK: - Cache\n\n    func setRegistryItems(_ items: [RegistryItem]) {\n        cachedRegistry = CachedRegistry(items: items)\n\n        // Set up timer to clear the cache after expiration\n        cleanupTimer?.invalidate()\n        cleanupTimer = Timer.scheduledTimer(\n            withTimeInterval: CachedRegistry.expirationInterval, repeats: false\n        ) { [weak self] _ in\n            Task { @MainActor in\n                guard let self = self else { return }\n                self.cachedRegistry = nil\n                self.cleanupTimer = nil\n                await self.downloadRegistryItems()\n            }\n        }\n\n        registryItems = items\n    }\n}\n\n/// `CachedRegistry` is a timer based cache that will remove the registry items from memory\n/// after a certain amount of time. This is because this memory is not needed for the majority of the\n/// lifetime of the application and can be freed when no longer used.\nprivate final class CachedRegistry {\n    let items: [RegistryItem]\n    let timestamp: Date\n\n    static let expirationInterval: TimeInterval = 300 // 5 minutes\n\n    init(items: [RegistryItem]) {\n        self.items = items\n        self.timestamp = Date()\n    }\n\n    var isExpired: Bool {\n        Date().timeIntervalSince(timestamp) > Self.expirationInterval\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Service/LSPService+Events.swift",
    "content": "//\n//  LSPService+Events.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 6/1/24.\n//\n\nimport Foundation\nimport LanguageClient\nimport LanguageServerProtocol\n\nextension LSPService {\n    func startListeningToEvents(for key: ClientKey) {\n        guard let languageClient = languageClients[key] else {\n            logger.error(\"Language client not found for \\(key.languageId.rawValue)\")\n            return\n        }\n\n        // Create a new Task to listen to the events\n        let task = Task.detached { [weak self] in\n            for await event in languageClient.lspInstance.eventSequence {\n                await self?.handleEvent(event, for: key)\n            }\n        }\n        eventListeningTasks[key] = task\n    }\n\n    func stopListeningToEvents(for key: ClientKey) {\n        if let task = eventListeningTasks[key] {\n            task.cancel()\n            eventListeningTasks.removeValue(forKey: key)\n        }\n    }\n\n    private func handleEvent(_ event: ServerEvent, for key: ClientKey) {\n        guard let client = languageClient(for: key.languageId, workspacePath: key.workspacePath) else {\n            return\n        }\n\n        switch event {\n        case let .request(_, request):\n            handleRequest(request, client: client)\n        case let .notification(notification):\n            handleNotification(notification, client: client)\n        case let .error(error):\n            logger.warning(\"Error from server \\(key.languageId.rawValue, privacy: .public): \\(error)\")\n        }\n    }\n\n    private func handleRequest(_ request: ServerRequest, client: LanguageServerType) {\n        // TODO: Handle Requests\n        switch request {\n            //        case let .workspaceConfiguration(params, _):\n            //            print(\"workspaceConfiguration: \\(params)\")\n            //        case let .workspaceFolders(handler):\n            //            print(\"workspaceFolders: \\(String(describing: handler))\")\n            //        case let .workspaceApplyEdit(params, _):\n            //            print(\"workspaceApplyEdit: \\(params)\")\n//        case let .clientRegisterCapability(params, _):\n//            print(\"clientRegisterCapability: \\(params)\")\n//        case let .clientUnregisterCapability(params, _):\n//            print(\"clientUnregisterCapability: \\(params)\")\n            //        case let .workspaceCodeLensRefresh(handler):\n            //            print(\"workspaceCodeLensRefresh: \\(String(describing: handler))\")\n//        case let .workspaceSemanticTokenRefresh(handler):\n//            print(\"Refresh semantic tokens!\", handler)\n            //        case let .windowShowMessageRequest(params, _):\n            //            print(\"windowShowMessageRequest: \\(params)\")\n            //        case let .windowShowDocument(params, _):\n            //            print(\"windowShowDocument: \\(params)\")\n            //        case let .windowWorkDoneProgressCreate(params, _):\n            //            print(\"windowWorkDoneProgressCreate: \\(params)\")\n        default:\n            return\n        }\n    }\n\n    private func handleNotification(_ notification: ServerNotification, client: LanguageServerType) {\n        // TODO: Handle Notifications\n        switch notification {\n        case let .windowLogMessage(message):\n            client.logContainer.appendLog(message)\n//        case let .windowShowMessage(params):\n//            print(\"windowShowMessage \\(params.type)\\n```\\n\\(params.message)\\n```\\n\")\n            //        case let .textDocumentPublishDiagnostics(params):\n            //            print(\"textDocumentPublishDiagnostics: \\(params)\")\n//        case let .telemetryEvent(params):\n//            print(\"telemetryEvent: \\(params)\")\n            //        case let .protocolCancelRequest(params):\n            //            print(\"protocolCancelRequest: \\(params)\")\n//        case let .protocolProgress(params):\n//            print(\"protocolProgress: \\(params)\")\n//        case let .protocolLogTrace(params):\n//            print(\"protocolLogTrace: \\(params)\")\n        default:\n            return\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Service/LSPService.swift",
    "content": "//\n//  LSPService.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/7/24.\n//\n\nimport os.log\nimport JSONRPC\nimport SwiftUI\nimport Foundation\nimport LanguageClient\nimport LanguageServerProtocol\nimport CodeEditLanguages\n\n/// `LSPService` is a service class responsible for managing the lifecycle and event handling\n/// of Language Server Protocol (LSP) clients within the CodeEdit application. It handles the initialization,\n/// communication, and termination of language servers, ensuring that code assistance features\n/// such as code completion, diagnostics, and more are available for various programming languages.\n///\n/// This class uses Swift's concurrency model to manage background tasks and event streams\n/// efficiently. Each language server runs in its own asynchronous task, listening for events and\n/// handling them as they occur. The `LSPService` class also provides functionality to start\n/// and stop individual language servers, as well as to stop all running servers.\n///\n/// ## Example Usage\n///\n/// ```swift\n/// @Service var lspService\n///\n/// try await lspService.startServer(\n///    for: .python,\n///    projectURL: projectURL,\n///    workspaceFolders: workspaceFolders\n/// )\n/// try await lspService.stopServer(for: .python)\n/// ```\n///\n/// ## Completion Example\n///\n/// ```swift\n/// func testCompletion() async throws {\n///     do {\n///         guard var languageClient = self.languageClient(for: .python) else {\n///             print(\"Failed to get client\")\n///             throw LSPServiceError.languageClientNotFound\n///         }\n///\n///         let testFilePathStr = \"\"\n///         let testFileURL = URL(fileURLWithPath: testFilePathStr)\n///\n///         // Tell server we opened a document\n///         _ = await languageClient.addDocument(testFileURL)\n///\n///         // Completion example\n///         let textPosition = Position(line: 32, character: 18)  // Lines and characters start at 0\n///         let completions = try await languageClient.requestCompletion(\n///             document: testFileURL.lspURI,\n///             position: textPosition\n///         )\n///         switch completions {\n///         case .optionA(let completionItems):\n///             // Handle the case where completions is an array of CompletionItem\n///             print(\"\\n*******\\nCompletion Items:\\n*******\\n\")\n///             for item in completionItems {\n///                 let textEdits = LSPCompletionItemsUtil.getCompletionItemEdits(\n///                     startPosition: textPosition,\n///                     item: item\n///                 )\n///                 for edit in textEdits {\n///                     print(edit)\n///                 }\n///             }\n///\n///         case .optionB(let completionList):\n///             // Handle the case where completions is a CompletionList\n///             print(\"\\n*******\\nCompletion Items:\\n*******\\n\")\n///             for item in completionList.items {\n///                 let textEdits = LSPCompletionItemsUtil.getCompletionItemEdits(\n///                     startPosition: textPosition,\n///                     item: item\n///                 )\n///                 for edit in textEdits {\n///                     print(edit)\n///                 }\n///             }\n///\n///             print(completionList.items[0])\n///\n///         case .none:\n///             print(\"No completions found\")\n///         }\n///\n///         // Close the document\n///         _ = await languageClient.closeDocument(testFilePathStr)\n///     } catch {\n///         print(error)\n///     }\n/// }\n/// ```\n@MainActor\nfinal class LSPService: ObservableObject {\n    typealias LanguageServerType = LanguageServer<CodeFileDocument>\n\n    let logger: Logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? \"\", category: \"LSPService\")\n\n    struct ClientKey: Hashable, Equatable {\n        let languageId: LanguageIdentifier\n        let workspacePath: String\n\n        init(_ languageId: LanguageIdentifier, _ workspacePath: String) {\n            self.languageId = languageId\n            self.workspacePath = workspacePath\n        }\n    }\n\n    /// Holds the active language clients\n    @Published var languageClients: [ClientKey: LanguageServerType] = [:]\n    /// Holds the language server configurations for all the installed language servers\n    var languageConfigs: [LanguageIdentifier: LanguageServerBinary] = [:]\n    /// Holds all the event listeners for each active language client\n    var eventListeningTasks: [ClientKey: Task<Void, Never>] = [:]\n\n    @AppSettings(\\.developerSettings.lspBinaries)\n    var lspBinaries\n\n    @Environment(\\.openWindow)\n    private var openWindow\n\n    init() {\n        // Load the LSP binaries from the developer menu\n        for binary in lspBinaries {\n            if let language = LanguageIdentifier(rawValue: binary.key) {\n                self.languageConfigs[language] = LanguageServerBinary(\n                    execPath: binary.value,\n                    args: [],\n                    env: ProcessInfo.processInfo.environment\n                )\n            }\n        }\n\n        NotificationCenter.default.addObserver(\n            forName: CodeFileDocument.didOpenNotification,\n            object: nil,\n            queue: .main\n        ) { notification in\n            MainActor.assumeIsolated {\n                guard let document = notification.object as? CodeFileDocument else { return }\n                self.openDocument(document)\n            }\n        }\n\n        NotificationCenter.default.addObserver(\n            forName: CodeFileDocument.didCloseNotification,\n            object: nil,\n            queue: .main\n        ) { notification in\n            MainActor.assumeIsolated {\n                guard let url = notification.object as? URL else { return }\n                self.closeDocument(url)\n            }\n        }\n    }\n\n    /// Gets the language server for the specified language and workspace.\n    func server(for languageId: LanguageIdentifier, workspacePath: String) -> InitializingServer? {\n        return languageClients[ClientKey(languageId, workspacePath)]?.lspInstance\n    }\n\n    /// Gets the language client for the specified language\n    func languageClient(for languageId: LanguageIdentifier, workspacePath: String) -> LanguageServerType? {\n        return languageClients[ClientKey(languageId, workspacePath)]\n    }\n\n    func languageClient(forDocument url: URL) -> LanguageServerType? {\n        languageClients.values.first(where: { $0.openFiles.document(for: url.lspURI) != nil })\n    }\n\n    // MARK: - Start Server\n\n    /// Given a language and workspace path, will attempt to start the language server\n    /// - Parameters:\n    ///   - languageId: The ID of the language server to start.\n    ///   - workspacePath: The workspace this language server is being used in.\n    /// - Returns: The new language server.\n    func startServer(\n        for languageId: LanguageIdentifier,\n        workspacePath: String\n    ) async throws -> LanguageServerType {\n        guard let serverBinary = languageConfigs[languageId] else {\n            logger.error(\"Couldn't find language sever binary for \\(languageId.rawValue)\")\n            throw LSPError.binaryNotFound\n        }\n\n        logger.info(\"Starting \\(languageId.rawValue) language server\")\n        let server = try await LanguageServerType.createServer(\n            for: languageId,\n            with: serverBinary,\n            workspacePath: workspacePath\n        )\n        languageClients[ClientKey(languageId, workspacePath)] = server\n        logger.info(\"Successfully started \\(languageId.rawValue) language server\")\n\n        self.startListeningToEvents(for: ClientKey(languageId, workspacePath))\n        return server\n    }\n\n    // MARK: - Document Management\n\n    /// Notify all relevant language clients that a document was opened.\n    /// - Note: Must be invoked after the contents of the file are available.\n    /// - Parameter document: The code document that was opened.\n    func openDocument(_ document: CodeFileDocument) {\n        guard let workspace = document.findWorkspace(),\n              let workspacePath = workspace.fileURL?.absolutePath,\n              let lspLanguage = document.getLanguage().lspLanguage else {\n            return\n        }\n        Task {\n            let languageServer: LanguageServerType\n            do {\n                if let server = self.languageClients[ClientKey(lspLanguage, workspacePath)] {\n                    languageServer = server\n                } else {\n                    languageServer = try await self.startServer(for: lspLanguage, workspacePath: workspacePath)\n                }\n            } catch {\n                notifyToInstallLanguageServer(language: lspLanguage)\n                // swiftlint:disable:next line_length\n                self.logger.error(\"Failed to find/start server for language: \\(lspLanguage.rawValue), workspace: \\(workspacePath, privacy: .private)\")\n                return\n            }\n            do {\n                try await languageServer.openDocument(document)\n            } catch {\n                let uri = document.languageServerURI\n                // swiftlint:disable:next line_length\n                self.logger.error(\"Failed to close document: \\(uri ?? \"<NO URI>\", privacy: .private), language: \\(lspLanguage.rawValue). Error \\(error)\")\n            }\n        }\n    }\n\n    /// Notify all relevant language clients that a document was closed.\n    /// - Parameter url: The url of the document that was closed\n    func closeDocument(_ url: URL) {\n        guard let languageClient = languageClient(forDocument: url) else { return }\n        Task {\n            do {\n                try await languageClient.closeDocument(url.lspURI)\n            } catch {\n                // swiftlint:disable:next line_length\n                logger.error(\"Failed to close document: \\(url.lspURI, privacy: .private), language: \\(languageClient.languageId.rawValue). Error \\(error)\")\n            }\n        }\n    }\n\n    // MARK: - Close Workspace\n\n    /// Close all language clients for a workspace.\n    ///\n    /// This is intentionally synchronous so we can exit from the workspace document's ``WorkspaceDocument/close()``\n    /// method ASAP.\n    ///\n    /// Errors thrown in this method are logged and otherwise not handled.\n    /// - Parameter workspacePath: The path of the workspace.\n    func closeWorkspace(_ workspacePath: String) {\n        Task {\n            let clientKeys = self.languageClients.filter({ $0.key.workspacePath == workspacePath })\n            for (key, languageClient) in clientKeys {\n                do {\n                    try await languageClient.shutdown()\n                } catch {\n                    logger.error(\"Failed to shutdown \\(key.languageId.rawValue) Language Server: Error \\(error)\")\n                }\n            }\n            for (key, _) in clientKeys {\n                self.languageClients.removeValue(forKey: key)\n            }\n        }\n    }\n\n    // MARK: - Stop Servers\n\n    /// Attempts to stop a running language server. Throws an error if the server is not found\n    /// or if the language server throws an error while trying to shutdown.\n    /// - Parameters:\n    ///   - languageId: The ID of the language server to stop.\n    ///   - workspacePath: The path of the workspace to stop the language server for.\n    func stopServer(forLanguage languageId: LanguageIdentifier, workspacePath: String) async throws {\n        guard let server = server(for: languageId, workspacePath: workspacePath) else {\n            logger.error(\"Server not found for language \\(languageId.rawValue) during stop operation\")\n            throw LSPServiceError.serverNotFound\n        }\n        do {\n            try await server.shutdownAndExit()\n        } catch {\n            logger.error(\"Failed to stop server for language \\(languageId.rawValue): \\(error.localizedDescription)\")\n            throw error\n        }\n        languageClients.removeValue(forKey: ClientKey(languageId, workspacePath))\n        logger.info(\"Server stopped for language \\(languageId.rawValue)\")\n\n        stopListeningToEvents(for: ClientKey(languageId, workspacePath))\n    }\n\n    /// Goes through all active language servers and attempts to shut them down.\n    func stopAllServers() async {\n        await withTaskGroup(of: Void.self) { group in\n            for (key, server) in languageClients {\n                group.addTask {\n                    do {\n                        try await server.shutdown()\n                    } catch {\n                        self.logger.warning(\"Shutting down \\(key.languageId.rawValue): Error \\(error)\")\n                    }\n                }\n            }\n        }\n        languageClients.removeAll()\n        eventListeningTasks.forEach { (_, value) in\n            value.cancel()\n        }\n        eventListeningTasks.removeAll()\n    }\n\n    /// Call this when a server is refusing to terminate itself. Sends the `SIGKILL` signal to all lsp processes.\n    func killAllServers() {\n        for (_, server) in languageClients {\n            kill(server.pid, SIGKILL)\n        }\n    }\n}\n\nextension LSPService {\n    private func notifyToInstallLanguageServer(language lspLanguage: LanguageIdentifier) {\n        // TODO: Re-Enable when this is more fleshed out (don't send duplicate notifications in a session)\n        return\n        // FIXME: Unreachable code - remove or re-enable when ready\n        /*\n        let lspLanguageTitle = lspLanguage.rawValue.capitalized\n        let notificationTitle = \"Install \\(lspLanguageTitle) Language Server\"\n        // Make sure the user doesn't have the same existing notification\n        guard !NotificationManager.shared.notifications.contains(where: { $0.title == notificationTitle }) else {\n            return\n        }\n\n        NotificationManager.shared.post(\n            iconSymbol: \"arrow.down.circle\",\n            iconColor: .clear,\n            title: notificationTitle,\n            description: \"Install the \\(lspLanguageTitle) language server to enable code intelligence features.\",\n            actionButtonTitle: \"Install\"\n        ) { [weak self] in\n            // TODO: Warning:\n            // Accessing Environment<OpenWindowAction>'s value outside of being installed on a View.\n            // This will always read the default value and will not update\n            self?.openWindow(sceneID: .settings)\n        }\n        */\n    }\n}\n\n// MARK: - Errors\n\nenum ServerManagerError: Error {\n    case serverNotFound\n    case serverStartFailed\n    case serverStopFailed\n    case languageClientNotFound\n}\n"
  },
  {
    "path": "CodeEdit/Features/LSP/Service/LSPServiceError.swift",
    "content": "//\n//  LSPServiceError.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 3/24/25.\n//\n\nenum LSPServiceError: Error {\n    case serverNotFound\n    case serverStartFailed\n    case serverStopFailed\n    case languageClientNotFound\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/FindNavigator/FindModePicker.swift",
    "content": "//\n//  FindModePicker.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 12/7/23.\n//\n\nimport SwiftUI\nimport Combine\n\nstruct FindModePicker: View {\n    var modes: [SearchModeModel]\n    private let onSelect: (SearchModeModel) -> Void\n    @Binding var selection: SearchModeModel\n\n    private let isLastItem: Bool\n\n    @Environment(\\.colorScheme)\n    var colorScheme\n\n    @Environment(\\.controlActiveState)\n    private var activeState\n\n    @EnvironmentObject var workspace: WorkspaceDocument\n\n    @State var position: NSPoint?\n    @State var isHovering: Bool = false\n    @State private var button: NSPopUpButton?\n\n    init(\n        modes: [SearchModeModel],\n        selection: Binding<SearchModeModel>,\n        onSelect: @escaping (SearchModeModel) -> Void,\n        isLastItem: Bool\n    ) {\n        self.modes = modes\n        self._selection = selection\n        self.onSelect = onSelect\n        self.isLastItem = isLastItem\n    }\n\n    var body: some View {\n        NSPopUpButtonView(selection: $selection, isOn: selection != modes.first) {\n            let button = NSPopUpButton()\n            button.menu = FindModeMenu(\n                modes: modes,\n                onSelect: onSelect\n            )\n            button.font = .systemFont(ofSize: NSFont.systemFontSize(for: .small))\n            button.isBordered = false\n            (button.cell as? NSPopUpButtonCell)?.arrowPosition = .noArrow\n            DispatchQueue.main.async {\n                self.button = button\n            }\n            return button\n        }\n        .fixedSize()\n        .frame(height: 21)\n        .padding(.trailing, 11)\n        .background {\n            Color(nsColor: colorScheme == .dark ? .white : .black)\n                .opacity(isHovering ? 0.05 : 0)\n                .clipShape(RoundedRectangle(cornerSize: CGSize(width: 4, height: 4)))\n            HStack {\n                Spacer()\n                if isHovering {\n                    chevronUpDown\n                        .padding(.trailing, 4)\n                } else if !isLastItem {\n                    chevron\n                        .padding(.trailing, 3)\n                }\n            }\n        }\n        .padding(.vertical, 3)\n        .onHover { hover in\n            isHovering = hover\n        }\n        .onTapGesture {\n            button?.performClick(nil)\n        }\n        .opacity(activeState != .inactive ? 1 : 0.75)\n    }\n\n    private var chevron: some View {\n        Image(systemName: \"chevron.compact.right\")\n            .font(.system(size: 9, weight: activeState != .inactive ? .medium : .bold, design: .default))\n            .foregroundStyle(.secondary)\n            .scaleEffect(x: 1.30, y: 1.0, anchor: .center)\n            .imageScale(.large)\n    }\n\n    private var chevronUpDown: some View {\n        VStack(spacing: 1) {\n            Image(systemName: \"chevron.up\")\n            Image(systemName: \"chevron.down\")\n        }\n        .font(.system(size: 6, weight: .bold, design: .default))\n        .padding(.top, 0.5)\n    }\n\n    struct NSPopUpButtonView<ItemType>: NSViewRepresentable where ItemType: Equatable {\n        @Binding var selection: ItemType\n        var isOn: Bool\n        var popupCreator: () -> NSPopUpButton\n\n        typealias NSViewType = NSPopUpButton\n\n        func makeNSView(context: NSViewRepresentableContext<NSPopUpButtonView>) -> NSPopUpButton {\n            let newPopupButton = popupCreator()\n            setPopUpFromSelection(newPopupButton, selection: selection)\n            if let menu = newPopupButton.menu {\n                context.coordinator.registerForChanges(in: menu)\n            }\n            return newPopupButton\n        }\n\n        func updateNSView(_ nsView: NSPopUpButton, context: NSViewRepresentableContext<NSPopUpButtonView>) {\n            setPopUpFromSelection(nsView, selection: selection)\n            nsView.contentTintColor = isOn ? .controlAccentColor : .controlTextColor\n        }\n\n        func setPopUpFromSelection(_ button: NSPopUpButton, selection: ItemType) {\n            let itemsList = button.itemArray\n            let matchedMenuItem = itemsList.filter {\n                ($0.representedObject as? ItemType) == selection\n            }.first\n            if matchedMenuItem != nil {\n                button.select(matchedMenuItem)\n            }\n        }\n\n        func makeCoordinator() -> Coordinator {\n            return Coordinator(self)\n        }\n\n        class Coordinator: NSObject {\n            var parent: NSPopUpButtonView\n\n            var cancellable: AnyCancellable?\n\n            init(_ parent: NSPopUpButtonView) {\n                self.parent = parent\n                super.init()\n            }\n\n            func registerForChanges(in menu: NSMenu) {\n                cancellable = NotificationCenter.default\n                    .publisher(for: NSMenu.didSendActionNotification, object: menu)\n                    .sink { [weak self] notification in\n                        if let menuItem = notification.userInfo?[\"MenuItem\"] as? NSMenuItem,\n                           let selection = menuItem as? ItemType {\n                            self?.parent.selection = selection\n                        }\n                    }\n            }\n        }\n    }\n}\n\nfinal class FindModeMenu: NSMenu, NSMenuDelegate {\n    private let modes: [SearchModeModel]\n    private let onSelect: (SearchModeModel) -> Void\n\n    init(\n        modes: [SearchModeModel],\n        onSelect: @escaping (SearchModeModel) -> Void\n    ) {\n        self.modes = modes\n        self.onSelect = onSelect\n        super.init(title: \"\")\n        delegate = self\n        modes.forEach { mode in\n            let menuItem = FindModeMenuItem(mode: mode, onSelect: onSelect)\n            menuItem.onStateImage = nil\n            self.addItem(menuItem)\n        }\n    }\n\n    @available(*, unavailable)\n    required init(coder _: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n}\n\nfinal class FindModeMenuItem: NSMenuItem {\n    private let mode: SearchModeModel\n    private let onSelect: (SearchModeModel) -> Void\n\n    init(\n        mode: SearchModeModel,\n        onSelect: @escaping (SearchModeModel) -> Void\n    ) {\n        self.mode = mode\n        self.onSelect = onSelect\n        super.init(title: mode.title, action: #selector(handleSelect), keyEquivalent: \"\")\n        target = self\n        representedObject = mode\n    }\n\n    @available(*, unavailable)\n    required init(coder _: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n    @objc\n    func handleSelect() {\n        onSelect(mode)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorForm.swift",
    "content": "//\n//  SearchModeSelector.swift\n//  CodeEdit\n//\n//  Created by Ziyuan Zhao on 2022/3/21.\n//\n\nimport SwiftUI\n\nstruct FindNavigatorForm: View {\n    @ObservedObject private var state: WorkspaceDocument.SearchState\n\n    @State private var selectedMode: [SearchModeModel] {\n        didSet {\n            // sync the variables, as selectedMode is an array\n            // and cannot be synced directly with @ObservedObject\n            state.selectedMode = selectedMode\n        }\n    }\n\n    @State private var includesText: String = \"\"\n    @State private var excludesText: String = \"\"\n    @State private var scoped: Bool = false\n    @State private var caseSensitive: Bool = false\n    @State private var preserveCase: Bool = false\n    @State private var scopedToOpenEditors: Bool = false\n    @State private var excludeSettings: Bool = true\n    @FocusState private var isSearchFieldFocused: Bool\n\n    init(state: WorkspaceDocument.SearchState) {\n        self.state = state\n        selectedMode = state.selectedMode\n    }\n\n    private var chevron: some View {\n        Image(systemName: \"chevron.compact.right\")\n            .foregroundStyle(.tertiary)\n            .imageScale(.large)\n    }\n\n    var body: some View {\n        VStack {\n            HStack {\n                HStack(spacing: 0) {\n                    ForEach(0..<selectedMode.count, id: \\.self) { index in\n                        FindModePicker(\n                            modes: getMenuList(index),\n                            selection: Binding(\n                                get: {\n                                    selectedMode[index]\n                                },\n                                set: { searchMode in\n                                    onSelectMenuItem(index, searchMode: searchMode)\n                                }\n                            ),\n                            onSelect: { searchMode in\n                                onSelectMenuItem(index, searchMode: searchMode)\n                            },\n                            isLastItem: index == selectedMode.count-1\n                        )\n                    }\n                    Spacer()\n                }\n                Spacer()\n                Text(\"Scoped\")\n                    .controlSize(.small)\n                    .foregroundStyle(Color(nsColor: scoped ? .controlAccentColor : .controlTextColor))\n                    .onTapGesture {\n                        scoped.toggle()\n                    }\n            }\n            .padding(.top, -5)\n            .padding(.bottom, -8)\n            PaneTextField(\n                state.selectedMode[1].title,\n                text: $state.searchQuery,\n                axis: .vertical,\n                leadingAccessories: {\n                    Image(systemName: \"magnifyingglass\")\n                        .padding(.leading, 8)\n                        .foregroundStyle(.tertiary)\n                        .font(.system(size: 12))\n                        .frame(width: 16, height: 20)\n                },\n                trailingAccessories: {\n                    Divider()\n                    Toggle(\n                        isOn: $caseSensitive,\n                        label: {\n                        Image(systemName: \"textformat\")\n                            .foregroundStyle(caseSensitive ? Color(.controlAccentColor) : Color(.secondaryLabelColor))\n                        }\n                    )\n                    .help(\"Match Case\")\n                    .onChange(of: caseSensitive) { _, newValue in\n                        state.caseSensitive = newValue\n                    }\n                },\n                clearable: true,\n                onClear: {\n                    state.clearResults()\n                },\n                hasValue: caseSensitive\n            )\n            .focused($isSearchFieldFocused)\n            .onSubmit {\n                if !state.searchQuery.isEmpty {\n                    Task {\n                        await state.search(state.searchQuery)\n                    }\n                } else {\n                    // If a user performs a search with an empty string, the search results will be cleared.\n                    // This behavior aligns with Xcode's handling of empty search queries.\n                    state.clearResults()\n                }\n            }\n            if selectedMode[0] == SearchModeModel.Replace {\n                PaneTextField(\n                    \"With\",\n                    text: $state.replaceText,\n                    axis: .vertical,\n                    leadingAccessories: {\n                        Image(systemName: \"arrow.2.squarepath\")\n                            .padding(.leading, 8)\n                            .foregroundStyle(.tertiary)\n                            .font(.system(size: 12))\n                            .frame(width: 16, height: 20)\n                    },\n                    trailingAccessories: {\n                        Divider()\n                        Toggle(\n                            isOn: $preserveCase,\n                            label: {\n                                Text(\"AB\")\n                                    .font(.system(size: 12, design: .rounded))\n                                    .foregroundStyle(\n                                        preserveCase ? Color(.controlAccentColor) : Color(.secondaryLabelColor)\n                                    )\n                            }\n                        )\n                        .help(\"Preserve Case\")\n                    },\n                    clearable: true,\n                    hasValue: preserveCase\n                )\n            }\n            if scoped {\n                PaneTextField(\n                    \"Only in folders\",\n                    text: $includesText,\n                    axis: .vertical,\n                    leadingAccessories: {\n                        Image(systemName: \"folder.badge.plus\")\n                            .padding(.leading, 8)\n                            .foregroundStyle(.tertiary)\n                            .font(.system(size: 12))\n                            .frame(width: 16, height: 20)\n                    },\n                    trailingAccessories: {\n                        Divider()\n                        Toggle(\n                            isOn: $scopedToOpenEditors,\n                            label: {\n                                Image(systemName: \"doc.plaintext\")\n                                    .foregroundStyle(\n                                        scopedToOpenEditors ? Color(.controlAccentColor) : Color(.secondaryLabelColor)\n                                    )\n                            }\n                        )\n                        .help(\"Search only in Open Editors\")\n                    },\n                    clearable: true,\n                    hasValue: scopedToOpenEditors\n                )\n                PaneTextField(\n                    \"Excluding folders\",\n                    text: $excludesText,\n                    axis: .vertical,\n                    leadingAccessories: {\n                        Image(systemName: \"folder.badge.minus\")\n                            .padding(.leading, 8)\n                            .foregroundStyle(.tertiary)\n                            .font(.system(size: 12))\n                            .frame(width: 16, height: 20)\n                    },\n                    trailingAccessories: {\n                        Divider()\n                        Toggle(\n                            isOn: $excludeSettings,\n                            label: {\n                                Image(systemName: \"gearshape\")\n                                    .foregroundStyle(\n                                        excludeSettings ? Color(.controlAccentColor) : Color(.secondaryLabelColor)\n                                    )\n                            }\n                        )\n                        .help(\"Use Exclude Settings and Ignore Files\")\n                    },\n                    clearable: true,\n                    hasValue: excludeSettings\n                )\n            }\n            if selectedMode[0] == SearchModeModel.Replace {\n                Button {\n                    Task {\n                        let startTime = Date()\n                        try? await state.findAndReplace(query: state.searchQuery, replacingTerm: state.replaceText)\n                        print(Date().timeIntervalSince(startTime))\n                    }\n                } label: {\n                    Text(\"Replace All\")\n                        .frame(maxWidth: .infinity)\n                }\n            }\n        }\n        .onReceive(state.$shouldFocusSearchField) { shouldFocus in\n            if shouldFocus {\n                isSearchFieldFocused = true\n                state.shouldFocusSearchField = false\n            }\n        }\n        .lineLimit(1...5)\n    }\n}\n\nextension FindNavigatorForm {\n    private func getMenuList(_ index: Int) -> [SearchModeModel] {\n        index == 0 ? SearchModeModel.SearchModes : selectedMode[index - 1].children\n    }\n\n    private func onSelectMenuItem(_ index: Int, searchMode: SearchModeModel) {\n        var newSelectedMode: [SearchModeModel] = []\n\n        switch index {\n        case 0:\n                newSelectedMode.append(searchMode)\n                self.updateSelectedMode(searchMode, searchModel: &newSelectedMode)\n                self.selectedMode = newSelectedMode\n        case 1:\n            if let firstMode = selectedMode.first {\n                newSelectedMode.append(contentsOf: [firstMode, searchMode])\n                if let thirdMode = searchMode.children.first {\n                    if let selectedThirdMode = selectedMode.third, searchMode.children.contains(selectedThirdMode) {\n                        newSelectedMode.append(selectedThirdMode)\n                    } else {\n                        newSelectedMode.append(thirdMode)\n                    }\n                }\n            }\n            self.selectedMode = newSelectedMode\n        case 2:\n            if let firstMode = selectedMode.first, let secondMode = selectedMode.second {\n                newSelectedMode.append(contentsOf: [firstMode, secondMode, searchMode])\n            }\n            self.selectedMode = newSelectedMode\n        default:\n            return\n        }\n    }\n\n    private func updateSelectedMode(_ searchMode: SearchModeModel, searchModel: inout [SearchModeModel]) {\n        if let secondMode = searchMode.children.first {\n            if let selectedSecondMode = selectedMode.second, searchMode.children.contains(selectedSecondMode) {\n                searchModel.append(contentsOf: selectedMode.dropFirst())\n            } else {\n                searchModel.append(secondMode)\n                if let thirdMode = secondMode.children.first, let selectedThirdMode = selectedMode.third {\n                    if secondMode.children.contains(selectedThirdMode) {\n                        searchModel.append(selectedThirdMode)\n                    } else {\n                        searchModel.append(thirdMode)\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorIndexBar.swift",
    "content": "//\n//  FindNavigatorIndexBar.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 12/4/23.\n//\n\nimport SwiftUI\n\nstruct FindNavigatorIndexBar: View {\n    @ObservedObject private var state: WorkspaceDocument.SearchState\n    @State private var progress: Double = 0.0\n    @State private var shouldShow: Bool = false\n\n    init(state: WorkspaceDocument.SearchState) {\n        self.state = state\n    }\n\n    var body: some View {\n        Group {\n            if shouldShow {\n                HStack(alignment: .center) {\n                    ProgressView(value: progress, total: 1.0) {\n                        EmptyView()\n                    } currentValueLabel: {\n                        HStack {\n                            Text(\"Indexing \\(Int(progress * 100))%\")\n                                .font(.system(size: 10))\n                                .animation(.none)\n                        }\n                    }\n                    // swiftlint:disable:next line_length\n                    .help(\"Indexing current workspace files for search. Searches performed while indexing may return incomplete results.\")\n                }\n                .transition(.asymmetric(insertion: .identity, removal: .move(edge: .top).combined(with: .opacity)))\n            }\n        }\n        .onAppear {\n            updateWithNewStatus(state.indexStatus)\n        }\n        .onReceive(state.$indexStatus) { newStatus in\n            updateWithNewStatus(newStatus)\n        }\n    }\n\n    /// Updates the bar with a new status update.\n    /// - Parameter status: The new status.\n    private func updateWithNewStatus(_ status: WorkspaceDocument.SearchState.IndexStatus) {\n        switch status {\n        case .none:\n            self.progress = 0.0\n            shouldShow = false\n        case .indexing(let progress):\n            if shouldShow {\n                withAnimation {\n                    self.progress = progress\n                }\n            } else {\n                shouldShow = true\n                self.progress = progress\n            }\n        case .done:\n            self.progress = 1.0\n            withAnimation(.default.delay(0.75)) {\n                shouldShow = false\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorResultList/FindNavigatorListViewController.swift",
    "content": "//\n//  FindNavigatorListViewController.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/7/22.\n//\n\nimport SwiftUI\n\nfinal class FindNavigatorListViewController: NSViewController {\n\n    public var workspace: WorkspaceDocument\n    public var selectedItem: Any?\n\n    private var searchItems: [SearchResultModel] = []\n    private var scrollView: NSScrollView!\n    private var outlineView: NSOutlineView!\n    private let prefs = Settings.shared.preferences\n    private var collapsedRows: Set<Int> = []\n\n    var rowHeight: Double = 22 {\n        didSet {\n            outlineView?.reloadData()\n        }\n    }\n\n    /// Setup the `scrollView` and `outlineView`\n    override func loadView() {\n        self.scrollView = NSScrollView()\n        self.view = scrollView\n\n        self.outlineView = NSOutlineView()\n        self.outlineView.dataSource = self\n        self.outlineView.delegate = self\n        self.outlineView.headerView = nil\n        self.outlineView.lineBreakMode = .byTruncatingTail\n\n        let column = NSTableColumn(identifier: .init(rawValue: \"Cell\"))\n        column.title = \"Cell\"\n        outlineView.addTableColumn(column)\n\n        self.scrollView.documentView = outlineView\n        self.scrollView.contentView.automaticallyAdjustsContentInsets = false\n        self.scrollView.contentView.contentInsets = .init(top: 0, left: 0, bottom: 0, right: 0)\n    }\n\n    init(workspace: WorkspaceDocument) {\n        self.workspace = workspace\n        super.init(nibName: nil, bundle: nil)\n    }\n\n    required init?(coder: NSCoder) {\n        fatalError(\"init?(coder: NSCoder) not implemented by FindNavigatorListViewController\")\n    }\n\n    override var acceptsFirstResponder: Bool { true }\n\n    /// Sets the search items for the view without loading anything.\n    /// - Parameter searchItems: The search items to set.\n    public func setSearchResults(_ searchItems: [SearchResultModel]) {\n        self.searchItems = searchItems\n    }\n\n    /// Updates the view with new search results and updates the UI.\n    /// - Parameter searchItems: The search items to set.\n    public func updateNewSearchResults(_ searchItems: [SearchResultModel]) {\n        self.searchItems = searchItems\n        outlineView.reloadData()\n        outlineView.expandItem(nil, expandChildren: true)\n\n        if let selectedItem {\n            selectSearchResult(selectedItem)\n        }\n    }\n\n    override func keyUp(with event: NSEvent) {\n        if event.charactersIgnoringModifiers == String(NSEvent.SpecialKey.delete.unicodeScalar) {\n            deleteSelectedItem()\n        }\n        super.keyUp(with: event)\n    }\n\n    /// Removes the selected item, called in response to an action like the backspace\n    /// character\n    private func deleteSelectedItem() {\n        let selectedRow = outlineView.selectedRow\n        guard selectedRow >= 0,\n              let selectedItem = outlineView.item(atRow: selectedRow) else { return }\n\n        if selectedItem is SearchResultMatchModel {\n            guard let parent = outlineView.parent(forItem: selectedItem) else { return }\n\n            // Remove the item from the search results\n            let parentIndex = outlineView.childIndex(forItem: parent)\n            let childIndex = outlineView.childIndex(forItem: selectedItem)\n            searchItems[parentIndex].lineMatches.remove(at: childIndex)\n\n            // If this was the last child, we need to remove the parent or we'll\n            // hit an exception\n            if searchItems[parentIndex].lineMatches.isEmpty {\n                searchItems.remove(at: parentIndex)\n                outlineView.removeItems(at: IndexSet([parentIndex]), inParent: nil)\n            } else {\n                outlineView.removeItems(at: IndexSet([childIndex]), inParent: parent)\n            }\n        } else {\n            let index = outlineView.childIndex(forItem: selectedItem)\n            searchItems.remove(at: index)\n            outlineView.removeItems(at: IndexSet([index]), inParent: nil)\n        }\n\n        outlineView.selectRowIndexes(IndexSet([selectedRow]), byExtendingSelection: false)\n    }\n\n    public func selectSearchResult(_ selectedItem: Any) {\n        let index = outlineView.row(forItem: selectedItem)\n        guard index >= 0 && index != outlineView.selectedRow else { return }\n        outlineView.selectRowIndexes(IndexSet([index]), byExtendingSelection: false)\n    }\n}\n\n// MARK: - NSOutlineViewDataSource\n\nextension FindNavigatorListViewController: NSOutlineViewDataSource {\n\n    func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {\n        if let item = item as? SearchResultModel {\n            return item.lineMatches.count\n        }\n        return searchItems.count\n    }\n\n    func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {\n        if let item = item as? SearchResultModel {\n            return item.lineMatches[index]\n        }\n        return searchItems[index]\n    }\n\n    func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {\n        if item is SearchResultModel {\n            return true\n        }\n        return false\n    }\n\n}\n\n// MARK: - NSOutlineViewDelegate\n\nextension FindNavigatorListViewController: NSOutlineViewDelegate {\n\n    func outlineView(\n        _ outlineView: NSOutlineView,\n        shouldShowCellExpansionFor tableColumn: NSTableColumn?,\n        item: Any\n    ) -> Bool {\n        item is SearchResultModel\n    }\n\n    func outlineView(_ outlineView: NSOutlineView, shouldShowOutlineCellForItem item: Any) -> Bool {\n        true\n    }\n\n    func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {\n        guard let tableColumn else { return nil }\n        if let item = item as? SearchResultMatchModel {\n            let frameRect = NSRect(x: 0, y: 0, width: tableColumn.width, height: outlineView.rowHeight)\n            return FindNavigatorListMatchCell(frame: frameRect, matchItem: item)\n        } else {\n            let frameRect = NSRect(\n                x: 0,\n                y: 0,\n                width: tableColumn.width,\n                height: prefs.general.projectNavigatorSize.rowHeight\n            )\n            let view = ProjectNavigatorTableViewCell(\n                frame: frameRect,\n                item: (item as? SearchResultModel)?.file,\n                isEditable: false\n            )\n            // We're using a medium label for file names b/c it makes it easier to\n            // distinguish quickly which results are from which files.\n            view.textField?.font = .systemFont(ofSize: 13, weight: .medium)\n            return view\n        }\n    }\n\n    func outlineViewSelectionDidChange(_ notification: Notification) {\n        guard let outlineView = notification.object as? NSOutlineView else {\n            return\n        }\n\n        let selectedIndex = outlineView.selectedRow\n\n        if let item = outlineView.item(atRow: selectedIndex) as? SearchResultMatchModel {\n            let selectedMatch = self.selectedItem as? SearchResultMatchModel\n            if selectedItem == nil || selectedMatch != item {\n                self.selectedItem = item\n                workspace.editorManager?.openTab(item: item.file)\n            }\n        } else if let item = outlineView.item(atRow: selectedIndex) as? SearchResultModel {\n            let selectedFile = self.selectedItem as? SearchResultModel\n            if selectedItem == nil || selectedFile != item {\n                self.selectedItem = item\n                workspace.editorManager?.openTab(item: item.file)\n            }\n        }\n    }\n\n    func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat {\n        if let matchItem = item as? SearchResultMatchModel {\n            guard let column = outlineView.tableColumns.first else {\n                return rowHeight\n            }\n            let columnWidth = column.width\n            let indentationLevel = outlineView.level(forItem: item)\n            let indentationSpace = CGFloat(indentationLevel) * outlineView.indentationPerLevel\n            let horizontalPaddingAndFixedElements: CGFloat = 24.0\n\n            let availableWidth = columnWidth - indentationSpace - horizontalPaddingAndFixedElements\n\n            guard availableWidth > 0 else {\n                // Not enough space to display anything, return minimum height\n                return max(rowHeight, Settings.shared.preferences.general.projectNavigatorSize.rowHeight)\n            }\n\n            let attributedString = matchItem.attributedLabel()\n\n            let tempView = NSTextField()\n            tempView.allowsEditingTextAttributes = true\n            tempView.attributedStringValue = attributedString\n\n            tempView.isEditable = false\n            tempView.isBordered = false\n            tempView.drawsBackground = false\n            tempView.alignment = .natural\n\n            tempView.cell?.wraps = true\n            tempView.cell?.usesSingleLineMode = false\n            tempView.lineBreakMode = .byWordWrapping\n            tempView.maximumNumberOfLines = Settings.shared.preferences.general.findNavigatorDetail.rawValue\n            tempView.preferredMaxLayoutWidth = availableWidth\n\n            var calculatedHeight = tempView.sizeThatFits(\n                NSSize(width: availableWidth, height: .greatestFiniteMagnitude)\n            ).height\n\n            // Total vertical padding (top + bottom) within the cell around the text\n            let verticalPaddingInCell: CGFloat = 8.0\n            calculatedHeight += verticalPaddingInCell\n            return max(calculatedHeight, self.rowHeight)\n        }\n        // For parent items\n        return prefs.general.projectNavigatorSize.rowHeight\n    }\n\n    func outlineViewColumnDidResize(_ notification: Notification) {\n        // Disable animations temporarily\n        NSAnimationContext.beginGrouping()\n        NSAnimationContext.current.duration = 0\n\n        var rowsToUpdate = IndexSet()\n        for row in 0..<outlineView.numberOfRows {\n            if let item = outlineView.item(atRow: row), item is SearchResultMatchModel {\n                rowsToUpdate.insert(row)\n            }\n        }\n        if !rowsToUpdate.isEmpty {\n            outlineView.noteHeightOfRows(withIndexesChanged: rowsToUpdate)\n        }\n\n        NSAnimationContext.endGrouping()\n        outlineView.layoutSubtreeIfNeeded()\n    }\n}\n\n// MARK: - NSMenuDelegate\n\nextension FindNavigatorListViewController: NSMenuDelegate {\n\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorResultList/FindNavigatorMatchListCell.swift",
    "content": "//\n//  FindNavigatorListCell.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/7/22.\n//\n\nimport SwiftUI\n\n/// A `NSTableCellView` showing an icon and label\nfinal class FindNavigatorListMatchCell: NSTableCellView {\n\n    private var label: NSTextField!\n    private var icon: NSImageView!\n    private var matchItem: SearchResultMatchModel\n\n    init(frame: CGRect, matchItem: SearchResultMatchModel) {\n        self.matchItem = matchItem\n        super.init(frame: CGRect(\n            x: frame.origin.x,\n            y: frame.origin.y,\n            width: frame.width,\n            height: frame.height\n        ))\n\n        // Create the label\n        setUpLabel()\n        setLabelString()\n\n        self.addSubview(label)\n\n        // Create the icon\n\n        setUpImageView()\n\n        self.addSubview(icon)\n        self.imageView = icon\n\n        // Constraints\n\n        NSLayoutConstraint.activate([\n            label.leadingAnchor.constraint(equalTo: icon.trailingAnchor, constant: 4),\n            label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -2),\n            label.topAnchor.constraint(equalTo: topAnchor, constant: 4),\n            label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -4),\n\n            icon.leadingAnchor.constraint(equalTo: leadingAnchor, constant: -2),\n            icon.topAnchor.constraint(equalTo: label.topAnchor, constant: 2),\n            icon.widthAnchor.constraint(equalToConstant: 16),\n            icon.heightAnchor.constraint(equalToConstant: 16)\n        ])\n    }\n\n    /// Sets up the `NSTextField` used as a label in the cell.\n    /// - Parameter frame: The frame the cell should use.\n    private func setUpLabel() {\n        self.label = NSTextField(wrappingLabelWithString: matchItem.lineContent)\n        self.label.translatesAutoresizingMaskIntoConstraints = false\n        self.label.drawsBackground = false\n        self.label.isEditable = false\n        self.label.isSelectable = false\n        self.label.layer?.cornerRadius = 10.0\n        self.label.font = .labelFont(ofSize: 13)\n        self.label.allowsDefaultTighteningForTruncation = false\n        self.label.cell?.truncatesLastVisibleLine = true\n        self.label.cell?.wraps = true\n        self.label.maximumNumberOfLines = 3\n    }\n\n    /// Sets up the image view for the search result.\n    private func setUpImageView() {\n        self.icon = NSImageView(frame: .zero)\n        self.icon.translatesAutoresizingMaskIntoConstraints = false\n        self.icon.symbolConfiguration = .init(\n            pointSize: 13,\n            weight: .regular,\n            scale: .medium\n        )\n        self.icon.image = NSImage(systemSymbolName: \"text.alignleft\", accessibilityDescription: nil)\n        self.icon.contentTintColor = NSColor.secondaryLabelColor\n    }\n\n    /// Sets the attributed string for the search result with correct paragraph break mode,\n    /// styling, font, etc.\n    private func setLabelString() {\n        self.label.attributedStringValue = matchItem.attributedLabel()\n    }\n\n    required init?(coder: NSCoder) {\n        fatalError()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorResultList/FindNavigatorResultList.swift",
    "content": "//\n//  SearchResultList.swift\n//  CodeEdit\n//\n//  Created by Ziyuan Zhao on 2022/3/22.\n//\n\nimport SwiftUI\nimport Combine\n\nstruct FindNavigatorResultList: NSViewControllerRepresentable {\n\n    @EnvironmentObject var workspace: WorkspaceDocument\n\n    @AppSettings(\\.general.projectNavigatorSize)\n    var projectNavigatorSize\n\n    typealias NSViewControllerType = FindNavigatorListViewController\n\n    func makeNSViewController(context: Context) -> FindNavigatorListViewController {\n        let controller = FindNavigatorListViewController(workspace: workspace)\n        controller.setSearchResults(workspace.searchState?.searchResult ?? [])\n        controller.rowHeight = projectNavigatorSize.rowHeight\n        context.coordinator.controller = controller\n        return controller\n    }\n\n    func updateNSViewController(_ nsViewController: FindNavigatorListViewController, context: Context) {\n        nsViewController.updateNewSearchResults(\n            workspace.searchState?.searchResult ?? []\n        )\n        if nsViewController.rowHeight != projectNavigatorSize.rowHeight {\n            nsViewController.rowHeight = projectNavigatorSize.rowHeight\n        }\n        return\n    }\n\n    func makeCoordinator() -> Coordinator {\n        Coordinator(\n            state: workspace.searchState,\n            controller: nil\n        )\n    }\n\n    class Coordinator: NSObject {\n        init(state: WorkspaceDocument.SearchState?, controller: FindNavigatorListViewController?) {\n            self.controller = controller\n            super.init()\n            self.listener = state?\n                .$searchResult\n                .sink(receiveValue: { [weak self] searchResults in\n                    self?.controller?.updateNewSearchResults(searchResults)\n                })\n        }\n\n        var listener: AnyCancellable?\n        var controller: FindNavigatorListViewController?\n\n        deinit {\n            controller = nil\n            listener?.cancel()\n            listener = nil\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorToolbarBottom.swift",
    "content": "//\n//  SourceControlToolbarBottom.swift\n//  CodeEdit\n//\n//  Created by Nanashi Li on 2022/05/20.\n//\n\nimport SwiftUI\n\nstruct FindNavigatorToolbarBottom: View {\n    @State private var text = \"\"\n\n    var body: some View {\n        HStack(spacing: 2) {\n            PaneTextField(\n                \"Filter\",\n                text: $text,\n                leadingAccessories: {\n                    Image(\n                        systemName: text.isEmpty\n                        ? \"line.3.horizontal.decrease.circle\"\n                        : \"line.3.horizontal.decrease.circle.fill\"\n                    )\n                    .foregroundStyle(\n                        text.isEmpty\n                        ? Color(nsColor: .secondaryLabelColor)\n                        : Color(nsColor: .controlAccentColor)\n                    )\n                    .padding(.leading, 4)\n                    .help(\"Show results with matching text\")\n                },\n                clearable: true\n            )\n        }\n        .frame(height: 28, alignment: .center)\n        .frame(maxWidth: .infinity)\n        .padding(.horizontal, 5)\n        .overlay(alignment: .top) {\n            Divider()\n                .opacity(0)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorView.swift",
    "content": "//\n//  FindNavigatorView.swift\n//  CodeEdit\n//\n//  Created by Ziyuan Zhao on 2022/3/20.\n//\n\nimport SwiftUI\n\nstruct FindNavigatorView: View {\n    @EnvironmentObject private var workspace: WorkspaceDocument\n\n    private var state: WorkspaceDocument.SearchState {\n        workspace.searchState ?? .init(workspace)\n    }\n\n    @State private var foundFilesCount: Int = 0\n    @State private var searchResultCount: Int = 0\n    @State private var findNavigatorStatus: WorkspaceDocument.SearchState.FindNavigatorStatus = .none\n    @State private var findResultMessage: String?\n\n    var body: some View {\n        VStack {\n            VStack {\n                FindNavigatorForm(state: state)\n                FindNavigatorIndexBar(state: state)\n            }\n            .padding(.horizontal, 10)\n            .padding(.vertical, 5)\n\n            Divider()\n\n            if findNavigatorStatus == .found {\n                HStack(alignment: .center) {\n                    Text(\"\\(self.searchResultCount) results in \\(self.foundFilesCount) files\")\n                        .font(.system(size: 10))\n                }\n\n                Divider()\n            }\n\n            switch self.findNavigatorStatus {\n            case .none:\n                Spacer()\n            case .searching:\n                VStack {\n                    ProgressView()\n                        .padding()\n\n                    Text(\"Searching\")\n                        .foregroundStyle(.tertiary)\n                        .font(.title3)\n                }\n                .frame(maxHeight: .infinity)\n            case .replacing:\n                VStack {\n                    ProgressView()\n                        .padding()\n\n                    Text(\"Replacing\")\n                        .foregroundStyle(.tertiary)\n                        .font(.title3)\n                }\n                .frame(maxHeight: .infinity)\n            case .found:\n                if self.searchResultCount == 0 {\n                    CEContentUnavailableView(\n                        \"No Results\",\n                        description: \"No Results for \\\"\\(state.searchQuery)\\\" in Project\",\n                        systemImage: \"exclamationmark.magnifyingglass\"\n                    )\n                } else {\n                    FindNavigatorResultList()\n                }\n            case .replaced(let updatedFiles):\n                CEContentUnavailableView(\n                    \"Replaced\",\n                    description: \"Successfully replaced terms across \\(updatedFiles) files\",\n                    systemImage: \"checkmark.circle.fill\"\n                )\n            case .failed(let errorMessage):\n                CEContentUnavailableView(\n                    \"An Error Occurred\",\n                    description: \"\\(errorMessage)\",\n                    systemImage: \"xmark.octagon.fill\"\n                )\n            }\n        }\n        .safeAreaInset(edge: .bottom, spacing: 0) {\n            FindNavigatorToolbarBottom()\n        }\n        .onReceive(state.$searchResult, perform: { value in\n            self.foundFilesCount = value.count\n        })\n        .onReceive(state.$searchResultsCount, perform: { value in\n            self.searchResultCount = value\n        })\n        .onReceive(state.$findNavigatorStatus, perform: { value in\n            self.findNavigatorStatus = value\n        })\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/Models/NavigatorTab.swift",
    "content": "//\n//  NavigatorTab.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 02/06/2023.\n//\n\nimport SwiftUI\nimport CodeEditKit\nimport ExtensionFoundation\n\nenum NavigatorTab: WorkspacePanelTab {\n    case project\n    case sourceControl\n    case search\n    case uiExtension(endpoint: AppExtensionIdentity, data: ResolvedSidebar.SidebarStore)\n\n    var systemImage: String {\n        switch self {\n        case .project:\n            return \"folder\"\n        case .sourceControl:\n            return \"vault\"\n        case .search:\n            return \"magnifyingglass\"\n        case .uiExtension(_, let data):\n            return data.icon ?? \"e.square\"\n        }\n    }\n\n    var id: String {\n        if case .uiExtension(let endpoint, let data) = self {\n            return endpoint.bundleIdentifier + data.sceneID\n        }\n        return title\n    }\n\n    var title: String {\n        switch self {\n        case .project:\n            return \"Project\"\n        case .sourceControl:\n            return \"Source Control\"\n        case .search:\n            return \"Search\"\n        case .uiExtension(_, let data):\n            return data.help ?? data.sceneID\n        }\n    }\n\n    var body: some View {\n        switch self {\n        case .project:\n            ProjectNavigatorView()\n        case .sourceControl:\n            SourceControlNavigatorView()\n        case .search:\n            FindNavigatorView()\n        case let .uiExtension(endpoint, data):\n            ExtensionSceneView(with: endpoint, sceneID: data.sceneID)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/OutlineView/FileSystemTableViewCell.swift",
    "content": "//\n//  FileSystemOutlineView.swift\n//  CodeEdit\n//\n//  Created by TAY KAI QUAN on 14/8/22.\n//\n\nimport SwiftUI\n\nclass FileSystemTableViewCell: StandardTableViewCell {\n\n    weak var fileItem: CEWorkspaceFile?\n\n    var changeLabelLargeWidth: NSLayoutConstraint!\n    var changeLabelSmallWidth: NSLayoutConstraint!\n\n    private let prefs = Settings.shared.preferences.general\n    private var navigatorFilter: String?\n\n    /// Initializes the `OutlineTableViewCell` with an `icon` and `label`\n    /// Both the icon and label will be colored, and sized based on the user's preferences.\n    /// - Parameters:\n    ///   - frameRect: The frame of the cell.\n    ///   - item: The file item the cell represents.\n    ///   - isEditable: Set to true if the user should be able to edit the file name.\n    ///   - navigatorFilter: An optional string use to filter the navigator area.\n    ///                      (Used for bolding and changing primary/secondary color).\n    init(frame frameRect: NSRect, item: CEWorkspaceFile?, isEditable: Bool = true, navigatorFilter: String? = nil) {\n        super.init(frame: frameRect, isEditable: isEditable)\n        self.navigatorFilter = navigatorFilter\n\n        if let item = item {\n            addIcon(item: item)\n        }\n        addModel()\n    }\n\n    override func configLabel(label: NSTextField, isEditable: Bool) {\n        super.configLabel(label: label, isEditable: isEditable)\n        label.delegate = self\n    }\n\n    func addIcon(item: CEWorkspaceFile) {\n        fileItem = item\n        imageView?.image = item.nsIcon\n        imageView?.contentTintColor = color(for: item)\n\n        let fileName = item.labelFileName()\n        let fontSize = textField?.font?.pointSize ?? 12\n\n        guard let filter = navigatorFilter?.trimmingCharacters(in: .whitespacesAndNewlines), !filter.isEmpty else {\n            textField?.stringValue = fileName\n            return\n        }\n\n        let paragraphStyle = NSMutableParagraphStyle()\n        paragraphStyle.lineBreakMode = .byTruncatingMiddle\n\n        /// Initialize default attributes\n        let attributedString = NSMutableAttributedString(string: fileName, attributes: [\n            .paragraphStyle: paragraphStyle,\n            .font: NSFont.systemFont(ofSize: fontSize),\n            .foregroundColor: NSColor.secondaryLabelColor\n        ])\n\n        /// Check if the filename contains the filter text\n        let range = (fileName as NSString).range(of: filter, options: .caseInsensitive)\n        if range.location != NSNotFound {\n            /// If the filter text matches, bold the matching text and set primary label color\n            attributedString.addAttributes(\n                [\n                    .font: NSFont.boldSystemFont(ofSize: fontSize),\n                    .foregroundColor: NSColor.labelColor\n                ],\n                range: range\n            )\n        } else {\n            /// If no match, apply primary label color for parent folder,\n            /// or secondary label color for a non-matching file\n            attributedString.addAttribute(\n                .foregroundColor,\n                value: item.isFolder ? NSColor.labelColor : NSColor.secondaryLabelColor,\n                range: NSRange(location: 0, length: attributedString.length)\n            )\n        }\n\n        textField?.attributedStringValue = attributedString\n    }\n\n    func addModel() {\n        guard let fileItem = fileItem, let secondaryLabel = secondaryLabel else {\n            return\n        }\n\n        if fileItem.url.isSymbolicLink { secondaryLabel.stringValue = \"􀰞\" }\n\n        guard let gitStatus = fileItem.gitStatus?.description else {\n            return\n        }\n\n        if gitStatus == \"?\" { secondaryLabel.stringValue += \"A\" } else {\n            secondaryLabel.stringValue += gitStatus\n        }\n    }\n\n    /// *Not Implemented*\n    override init(frame frameRect: NSRect) {\n        super.init(frame: frameRect)\n        fatalError(\"\"\"\n            init(frame: ) isn't implemented on `OutlineTableViewCell`.\n            Please use `.init(frame: NSRect, item: FileSystemClient.FileItem?)\n            \"\"\")\n    }\n\n    /// *Not Implemented*\n    required init?(coder: NSCoder) {\n        fatalError(\"\"\"\n            init?(coder: NSCoder) isn't implemented on `OutlineTableViewCell`.\n            Please use `.init(frame: NSRect, item: FileSystemClient.FileItem?)\n            \"\"\")\n    }\n\n    /// Returns the font size for the current row height. Defaults to `13.0`\n    private var fontSize: Double {\n        switch self.frame.height {\n        case 20: return 11\n        case 22: return 13\n        case 24: return 14\n        default: return 13\n        }\n    }\n\n    /// Get the appropriate color for the items icon depending on the users preferences.\n    /// - Parameter item: The `FileItem` to get the color for\n    /// - Returns: A `NSColor` for the given `FileItem`.\n    func color(for item: CEWorkspaceFile) -> NSColor {\n        if prefs.fileIconStyle == .color {\n            if !item.isFolder {\n                return NSColor(item.iconColor)\n            } else {\n                return NSColor.folderBlue\n            }\n        } else {\n            return NSColor.coolGray\n        }\n    }\n\n    deinit {\n        toolTip = nil\n    }\n}\n\nlet errorRed = NSColor(red: 1, green: 0, blue: 0, alpha: 0.2)\nextension FileSystemTableViewCell: NSTextFieldDelegate {\n    func controlTextDidChange(_ obj: Notification) {\n        guard let fileItem else { return }\n        textField?.backgroundColor = fileItem.validateFileName(for: textField?.stringValue ?? \"\") ? .none : errorRed\n    }\n\n    func controlTextDidEndEditing(_ obj: Notification) {\n        guard let fileItem else { return }\n        do {\n            textField?.backgroundColor = fileItem.validateFileName(for: textField?.stringValue ?? \"\") ? .none : errorRed\n            if fileItem.validateFileName(for: textField?.stringValue ?? \"\") {\n                let newURL = fileItem.url\n                    .deletingLastPathComponent()\n                    .appending(path: textField?.stringValue ?? \"\")\n                try workspace?.workspaceFileManager?.move(file: fileItem, to: newURL)\n            } else {\n                textField?.stringValue = fileItem.labelFileName()\n            }\n        } catch {\n            let alert = NSAlert(error: error)\n            alert.addButton(withTitle: \"Dismiss\")\n            alert.runModal()\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/OutlineView/StandardTableViewCell.swift",
    "content": "//\n//  StandardTableViewCell.swift\n//  CodeEdit\n//\n//  Created by TAY KAI QUAN on 17/8/22.\n//\n\nimport SwiftUI\n\nclass StandardTableViewCell: NSTableCellView {\n\n    weak var secondaryLabel: NSTextField?\n    weak var workspace: WorkspaceDocument?\n\n    var secondaryLabelRightAligned: Bool = true {\n        didSet {\n            resizeSubviews(withOldSize: .zero)\n        }\n    }\n\n    /// Initializes the `TableViewCell` with an `icon` and `label`\n    /// Both the icon and label will be colored, and sized based on the user's preferences.\n    /// - Parameters:\n    ///   - frameRect: The frame of the cell.\n    ///   - item: The file item the cell represents.\n    ///   - isEditable: Set to true if the user should be able to edit the file name.\n    init(frame frameRect: NSRect, isEditable: Bool = true) {\n        super.init(frame: frameRect)\n        setupViews(frame: frameRect, isEditable: isEditable)\n\n    }\n\n    // Default init, assumes isEditable to be false\n    override init(frame frameRect: NSRect) {\n        super.init(frame: frameRect)\n        setupViews(frame: frameRect, isEditable: false)\n    }\n\n    private func setupViews(frame frameRect: NSRect, isEditable: Bool) {\n        // Create the label\n        let label = createLabel()\n        configLabel(label: label, isEditable: isEditable)\n        self.textField = label\n\n        // Create the secondary label\n        let secondaryLabel = createSecondaryLabel()\n        configSecondaryLabel(secondaryLabel: secondaryLabel)\n        self.secondaryLabel = secondaryLabel\n\n        // Create the icon\n        let icon = createIcon()\n        configIcon(icon: icon)\n        addSubview(icon)\n        imageView = icon\n\n        // add constraints\n        createConstraints(frame: frameRect)\n        addSubview(label)\n        addSubview(secondaryLabel)\n        addSubview(icon)\n    }\n\n    // MARK: Create and config stuff\n    func createLabel() -> NSTextField {\n        return SpecialSelectTextField(frame: .zero)\n    }\n\n    func configLabel(label: NSTextField, isEditable: Bool) {\n        label.translatesAutoresizingMaskIntoConstraints = false\n        label.drawsBackground = false\n        label.isBordered = false\n        label.isEditable = isEditable\n        label.isSelectable = isEditable\n        label.layer?.cornerRadius = 10.0\n        label.font = .labelFont(ofSize: fontSize)\n        label.lineBreakMode = .byTruncatingMiddle\n    }\n\n    func createSecondaryLabel() -> NSTextField {\n        return NSTextField(frame: .zero)\n    }\n\n    func configSecondaryLabel(secondaryLabel: NSTextField) {\n        secondaryLabel.translatesAutoresizingMaskIntoConstraints = false\n        secondaryLabel.drawsBackground = false\n        secondaryLabel.isBordered = false\n        secondaryLabel.isEditable = false\n        secondaryLabel.isSelectable = false\n        secondaryLabel.layer?.cornerRadius = 10.0\n        secondaryLabel.font = .systemFont(ofSize: fontSize-2, weight: .bold)\n        secondaryLabel.alignment = .center\n        secondaryLabel.textColor = .secondaryLabelColor\n    }\n\n    func createIcon() -> NSImageView {\n        return NSImageView(frame: .zero)\n    }\n\n    func configIcon(icon: NSImageView) {\n        icon.translatesAutoresizingMaskIntoConstraints = false\n        icon.symbolConfiguration = .init(pointSize: fontSize, weight: .regular, scale: .medium)\n    }\n\n    func createConstraints(frame frameRect: NSRect) {\n        resizeSubviews(withOldSize: .zero)\n    }\n\n    let iconWidth: CGFloat = 22\n    override func resizeSubviews(withOldSize oldSize: NSSize) {\n        super.resizeSubviews(withOldSize: oldSize)\n        guard let imageView, textField != nil, secondaryLabel != nil else {\n            assertionFailure(\n                \"Missing child view:\"\n                + \" imageView \\(imageView == nil)\"\n                + \", textField: \\(textField == nil)\"\n                + \", label: \\(secondaryLabel == nil)\"\n            )\n            return\n        }\n\n        imageView.frame = NSRect(\n            x: 2,\n            y: 4,\n            width: iconWidth,\n            height: frame.height\n        )\n        // center align the image\n        if let alignmentRect = imageView.image?.alignmentRect {\n            imageView.frame = NSRect(\n                x: (iconWidth - alignmentRect.width) / 2,\n                y: 4,\n                width: alignmentRect.width,\n                height: frame.height\n            )\n        }\n\n        // right align the secondary label\n        if secondaryLabelRightAligned {\n            rightAlignSecondary()\n        } else {\n            // put the secondary label right after the primary label\n            leftAlignSecondary()\n        }\n    }\n\n    private func rightAlignSecondary() {\n        guard let secondaryLabel, let textField, let imageView else { return }\n        let secondLabelWidth = secondaryLabel.frame.size.width\n        let newSize = secondaryLabel.sizeThatFits(\n            CGSize(width: secondLabelWidth, height: CGFloat.greatestFiniteMagnitude)\n        )\n        // somehow, a width of 0 makes it resize properly.\n        secondaryLabel.frame = NSRect(\n            x: frame.width - newSize.width,\n            y: 3.5,\n            width: 0,\n            height: newSize.height\n        )\n\n        textField.frame = NSRect(\n            x: iconWidth + 2,\n            y: 3.5,\n            width: secondaryLabel.frame.minX - imageView.frame.maxX - 5,\n            height: 25\n        )\n    }\n\n    private func leftAlignSecondary() {\n        guard let secondaryLabel, let textField else { return }\n        let mainLabelWidth = textField.frame.size.width\n        let newSize = textField.sizeThatFits(CGSize(width: mainLabelWidth, height: CGFloat.greatestFiniteMagnitude))\n        textField.frame = NSRect(\n            x: iconWidth + 2,\n            y: 2.5,\n            width: newSize.width,\n            height: 25\n        )\n        secondaryLabel.frame = NSRect(\n            x: textField.frame.maxX + 2,\n            y: 2.5,\n            width: frame.width - textField.frame.maxX - 2,\n            height: 25\n        )\n    }\n\n    /// *Not Implemented*\n    required init?(coder: NSCoder) {\n        fatalError(\"\"\"\n            init?(coder: NSCoder) isn't implemented on `StandardTableViewCell`.\n            Please use `.init(frame: NSRect, isEditable: Bool)\n            \"\"\")\n    }\n\n    /// Returns the font size for the current row height. Defaults to `13.0`\n    private var fontSize: Double {\n        switch self.frame.height {\n        case 20: return 11\n        case 22: return 13\n        case 24: return 14\n        default: return 13\n        }\n    }\n\n    class SpecialSelectTextField: NSTextField {\n        override func becomeFirstResponder() -> Bool {\n            let range = NSRange(\n                location: 0,\n                length: stringValue.distance(\n                    from: stringValue.startIndex,\n                    to: stringValue.lastIndex(of: \".\") ?? stringValue.endIndex\n                )\n            )\n            selectText(self)\n            let editor = currentEditor()\n            editor?.selectedRange = range\n            return true\n        }\n\n        override func textDidBeginEditing(_ notification: Notification) {\n            super.textDidBeginEditing(notification)\n            wantsLayer = true\n            layer?.backgroundColor = NSColor.textBackgroundColor.cgColor\n        }\n\n        override func textDidEndEditing(_ notification: Notification) {\n            super.textDidEndEditing(notification)\n            wantsLayer = false\n            layer?.backgroundColor = nil\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/OutlineView/TextTableViewCell.swift",
    "content": "//\n//  TextTableViewCell.swift\n//  CodeEdit\n//\n//  Created by TAY KAI QUAN on 11/9/22.\n//\n\nimport SwiftUI\n\nclass TextTableViewCell: NSTableCellView {\n\n    var label: NSTextField!\n\n    init(frame frameRect: NSRect, isEditable: Bool = true, startingText: String = \"\") {\n        super.init(frame: frameRect)\n        setupViews(frame: frameRect, isEditable: isEditable)\n        self.label.stringValue = startingText\n    }\n\n    // Default init, assumes isEditable to be false\n    override init(frame frameRect: NSRect) {\n        super.init(frame: frameRect)\n        setupViews(frame: frameRect, isEditable: false)\n    }\n\n    private func setupViews(frame frameRect: NSRect, isEditable: Bool) {\n        // Create the label\n        label = createLabel()\n        configLabel(label: self.label, isEditable: isEditable)\n        self.textField = label\n\n        addSubview(label)\n        createConstraints(frame: frameRect)\n    }\n\n    // MARK: Create and config stuff\n    func createLabel() -> NSTextField {\n        return NSTextField(frame: .zero)\n    }\n\n    func configLabel(label: NSTextField, isEditable: Bool) {\n        label.translatesAutoresizingMaskIntoConstraints = false\n        label.drawsBackground = false\n        label.isBordered = false\n        label.isEditable = isEditable\n        label.isSelectable = isEditable\n        label.layer?.cornerRadius = 10.0\n        label.font = .boldSystemFont(ofSize: fontSize)\n        label.lineBreakMode = .byTruncatingMiddle\n        label.textColor = NSColor.textColor\n        label.alphaValue = 0.7\n    }\n\n    func createConstraints(frame frameRect: NSRect) {\n        resizeSubviews(withOldSize: .zero)\n    }\n\n    override func resizeSubviews(withOldSize oldSize: NSSize) {\n        super.resizeSubviews(withOldSize: oldSize)\n        label.frame = NSRect(\n            x: 2,\n            y: 2.5,\n            width: frame.width - 4,\n            height: 25\n        )\n    }\n\n    /// Returns the font size for the current row height. Defaults to `13.0`\n    private var fontSize: Double {\n        switch self.frame.height {\n        case 20: return 11\n        case 22: return 13\n        case 24: return 14\n        default: return 13\n        }\n    }\n\n    /// *Not Implemented*\n    required init(coder: NSCoder) {\n        fatalError(\"\"\"\n            init?(coder: NSCoder) isn't implemented on `TextTableViewCell`.\n            Please use `.init(frame: NSRect, isEditable: Bool)\n            \"\"\")\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenu.swift",
    "content": "//\n//  OutlineMenu.swift\n//  CodeEdit\n//\n//  Created by Lukas Pistrol on 07.04.22.\n//\n\nimport SwiftUI\nimport UniformTypeIdentifiers\n\n/// A subclass of `NSMenu` implementing the contextual menu for the project navigator\nfinal class ProjectNavigatorMenu: NSMenu {\n\n    /// The item to show the contextual menu for\n    var item: CEWorkspaceFile?\n\n    /// The workspace, for opening the item\n    var workspace: WorkspaceDocument?\n\n    /// The  `ProjectNavigatorViewController` is being called from.\n    /// By sending it, we can access it's variables and functions.\n    var sender: ProjectNavigatorViewController\n\n    init(_ sender: ProjectNavigatorViewController) {\n        self.sender = sender\n        super.init(title: \"Options\")\n    }\n\n    @available(*, unavailable)\n    required init(coder _: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n    /// Creates a `NSMenuItem` depending on the given arguments\n    /// - Parameters:\n    ///   - title: The title of the menu item\n    ///   - action: A `Selector` or `nil` of the action to perform.\n    ///   - key: A `keyEquivalent` of the menu item. Defaults to an empty `String`\n    /// - Returns: A `NSMenuItem` which has the target `self`\n    private func menuItem(_ title: String, action: Selector?, key: String = \"\") -> NSMenuItem {\n        let mItem = NSMenuItem(title: title, action: action, keyEquivalent: key)\n        mItem.target = self\n\n        return mItem\n    }\n\n    /// Configures the menu based on the current selection in the outline view.\n    /// - Menu items get added depending on the amount of selected items.\n    private func setupMenu() { // swiftlint:disable:this function_body_length\n        guard let item else { return }\n        let showInFinder = menuItem(\"Show in Finder\", action: #selector(showInFinder))\n\n        let openInTab = menuItem(\"Open in Tab\", action: #selector(openInTab))\n        let openInNewWindow = menuItem(\"Open in New Window\", action: nil)\n        let openExternalEditor = menuItem(\"Open with External Editor\", action: #selector(openWithExternalEditor))\n        let openAs = menuItem(\"Open As\", action: nil)\n\n        let copyPath = menuItem(\"Copy Path\", action: #selector(copyPath))\n        let copyRelativePath = menuItem(\"Copy Relative Path\", action: #selector(copyRelativePath))\n\n        let showFileInspector = menuItem(\"Show File Inspector\", action: nil)\n\n        let newFile = menuItem(\"New File...\", action: #selector(newFile))\n        let newFileFromClipboard = menuItem(\n            \"New File from Clipboard\",\n            action: #selector(newFileFromClipboard),\n            key: \"v\"\n        )\n        newFileFromClipboard.keyEquivalentModifierMask = [.command]\n        let newFolder = menuItem(\"New Folder\", action: #selector(newFolder))\n\n        let rename = menuItem(\"Rename\", action: #selector(renameFile))\n\n        let trash = menuItem(\"Move to Trash\", action:\n                                item.url != workspace?.workspaceFileManager?.folderUrl\n                              ? #selector(trash) : nil)\n\n        // trash has to be the previous menu item for delete.isAlternate to work correctly\n        let delete = menuItem(\"Delete Immediately...\", action:\n                                item.url != workspace?.workspaceFileManager?.folderUrl\n                              ? #selector(delete) : nil)\n        delete.keyEquivalentModifierMask = .option\n        delete.isAlternate = true\n\n        let duplicate = menuItem(\"Duplicate \\(item.isFolder ? \"Folder\" : \"File\")\", action: #selector(duplicate))\n\n        let sortByName = menuItem(\"Sort by Name\", action: nil)\n        sortByName.isEnabled = item.isFolder\n\n        let sortByType = menuItem(\"Sort by Type\", action: nil)\n        sortByType.isEnabled = item.isFolder\n\n        let sourceControl = menuItem(\"Source Control\", action: nil)\n\n        items = [\n            showInFinder,\n            NSMenuItem.separator(),\n            openInTab,\n            openInNewWindow,\n            openExternalEditor,\n            openAs,\n            NSMenuItem.separator(),\n            copyPath,\n            copyRelativePath,\n            NSMenuItem.separator(),\n            showFileInspector,\n            NSMenuItem.separator(),\n            newFile,\n            newFileFromClipboard,\n            newFolder\n        ]\n\n        if canCreateFolderFromSelection() {\n            items.append(menuItem(\"New Folder from Selection\", action: #selector(newFolderFromSelection)))\n        }\n        items.append(NSMenuItem.separator())\n        if selectedItems().count == 1 {\n            items.append(rename)\n        }\n\n        items.append(\n            contentsOf: [\n                trash,\n                delete,\n                duplicate,\n                NSMenuItem.separator(),\n                sortByName,\n                sortByType,\n                NSMenuItem.separator(),\n                sourceControl,\n            ]\n        )\n\n        setSubmenu(openAsMenu(item: item), for: openAs)\n        setSubmenu(sourceControlMenu(item: item), for: sourceControl)\n    }\n\n    /// Submenu for **Open As** menu item.\n    private func openAsMenu(item: CEWorkspaceFile) -> NSMenu {\n        let openAsMenu = NSMenu(title: \"Open As\")\n        func getMenusItems() -> ([NSMenuItem], [NSMenuItem]) {\n            // Use UTType to distinguish between bundle file and user-browsable directory\n            // The isDirectory property is not accurate on this.\n            guard let type = item.contentType else { return ([.none()], []) }\n            if type.conforms(to: .folder) {\n                return ([.none()], [])\n            }\n            var primaryItems = [NSMenuItem]()\n            if type.conforms(to: .sourceCode) {\n                primaryItems.append(.sourceCode())\n            }\n            if type.conforms(to: .propertyList) {\n                primaryItems.append(.propertyList())\n            }\n            if type.conforms(to: UTType(filenameExtension: \"xcassets\")!) {\n                primaryItems.append(NSMenuItem(title: \"Asset Catalog Document\", action: nil, keyEquivalent: \"\"))\n            }\n            if type.conforms(to: UTType(filenameExtension: \"xib\")!) {\n                primaryItems.append(NSMenuItem(title: \"Interface Builder XIB Document\", action: nil, keyEquivalent: \"\"))\n            }\n            if type.conforms(to: UTType(filenameExtension: \"xcodeproj\")!) {\n                primaryItems.append(NSMenuItem(title: \"Xcode Project\", action: nil, keyEquivalent: \"\"))\n            }\n            var secondaryItems = [NSMenuItem]()\n            if type.conforms(to: .text) {\n                secondaryItems.append(.asciiPropertyList())\n                secondaryItems.append(.hex())\n            }\n\n            // FIXME: Update the quickLook condition\n            if type.conforms(to: .data) {\n                secondaryItems.append(.quickLook())\n            }\n\n            return (primaryItems, secondaryItems)\n        }\n        let (primaryItems, secondaryItems) = getMenusItems()\n        for item in primaryItems {\n            openAsMenu.addItem(item)\n        }\n        if !secondaryItems.isEmpty {\n            openAsMenu.addItem(.separator())\n        }\n        for item in secondaryItems {\n            openAsMenu.addItem(item)\n        }\n        return openAsMenu\n    }\n\n    /// Submenu for **Source Control** menu item.\n    private func sourceControlMenu(item: CEWorkspaceFile) -> NSMenu {\n        let sourceControlMenu = NSMenu(title: \"Source Control\")\n        sourceControlMenu.addItem(\n            withTitle: \"Commit \\\"\\(String(describing: item.fileName()))\\\"...\",\n            action: nil,\n            keyEquivalent: \"\"\n        )\n        sourceControlMenu.addItem(.separator())\n        sourceControlMenu.addItem(withTitle: \"Discard Changes...\", action: nil, keyEquivalent: \"\")\n        sourceControlMenu.addItem(.separator())\n        sourceControlMenu.addItem(withTitle: \"Add Selected Files\", action: nil, keyEquivalent: \"\")\n        sourceControlMenu.addItem(withTitle: \"Mark Selected Files as Resolved\", action: nil, keyEquivalent: \"\")\n\n        return sourceControlMenu\n    }\n\n    /// Updates the menu for the selected item and hides it if no item is provided.\n    override func update() {\n        removeAllItems()\n        setupMenu()\n    }\n}\n\nextension NSMenuItem {\n    fileprivate static func none() -> NSMenuItem {\n        let item = NSMenuItem(title: \"<None>\", action: nil, keyEquivalent: \"\")\n        item.isEnabled = false\n        return item\n    }\n\n    fileprivate static func sourceCode() -> NSMenuItem {\n        NSMenuItem(title: \"Source Code\", action: nil, keyEquivalent: \"\")\n    }\n\n    fileprivate static func propertyList() -> NSMenuItem {\n        NSMenuItem(title: \"Property List\", action: nil, keyEquivalent: \"\")\n    }\n\n    fileprivate static func asciiPropertyList() -> NSMenuItem {\n        NSMenuItem(title: \"ASCII Property List\", action: nil, keyEquivalent: \"\")\n    }\n\n    fileprivate static func hex() -> NSMenuItem {\n        NSMenuItem(title: \"Hex\", action: nil, keyEquivalent: \"\")\n    }\n\n    fileprivate static func quickLook() -> NSMenuItem {\n        NSMenuItem(title: \"Quick Look\", action: nil, keyEquivalent: \"\")\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift",
    "content": "//\n//  ProjectNavigatorMenuActions.swift\n//  CodeEdit\n//\n//  Created by Leonardo Larrañaga on 10/11/24.\n//\n\nimport AppKit\nimport SwiftUI\n\nextension ProjectNavigatorMenu {\n    /// - Returns: the currently selected `CEWorkspaceFile` items in the outline view.\n    func selectedItems() -> Set<CEWorkspaceFile> {\n        /// Selected items...\n        let selectedItems = Set(sender.outlineView.selectedRowIndexes.compactMap {\n            sender.outlineView.item(atRow: $0) as? CEWorkspaceFile\n        })\n\n        /// Item that the user brought up the menu with...\n        if let menuItem = sender.outlineView.item(atRow: sender.outlineView.clickedRow) as? CEWorkspaceFile {\n            /// If the item is not in the set, just like in Xcode, only modify that item.\n            if !selectedItems.contains(menuItem) {\n                return Set([menuItem])\n            }\n        }\n\n        return selectedItems\n    }\n\n    /// Verify if a folder can be made from selection by getting the amount of parents found in the selected items.\n    /// If the amount of parents is equal to one, a folder can be made.\n    func canCreateFolderFromSelection() -> Bool {\n        var uniqueParents: Set<CEWorkspaceFile> = []\n        for file in selectedItems() {\n            if let parent = file.parent {\n                uniqueParents.insert(parent)\n            }\n        }\n\n        return uniqueParents.count == 1\n    }\n\n    /// Action that opens **Finder** at the items location.\n    @objc\n    func showInFinder() {\n        NSWorkspace.shared.activateFileViewerSelecting(selectedItems().map { $0.url })\n    }\n\n    /// Action that opens the item, identical to clicking it.\n    @objc\n    func openInTab() {\n        /// Sort the selected items first by their parent and then by name.\n        let sortedItems = selectedItems().sorted { (item1, item2) -> Bool in\n            /// Get the parents of both items.\n            let parent1 = sender.outlineView.parent(forItem: item1) as? CEWorkspaceFile\n            let parent2 = sender.outlineView.parent(forItem: item2) as? CEWorkspaceFile\n\n            /// Compare by parent.\n            if parent1 != parent2 {\n                /// If the parents are different, use their row position in the outline view.\n                return sender.outlineView.row(forItem: parent1) < sender.outlineView.row(forItem: parent2)\n            } else {\n                /// If both items have the same parent, sort them by name.\n                return item1.name < item2.name\n            }\n        }\n\n        /// Open the items in order.\n        sortedItems.forEach { item in\n            workspace?.editorManager?.openTab(item: item)\n        }\n    }\n\n    /// Action that opens in an external editor\n    @objc\n    func openWithExternalEditor() {\n        /// Using  `Process` to open all of the selected files at the same time.\n        let process = Process()\n        process.launchPath = \"/usr/bin/open\"\n        process.arguments = selectedItems().map { $0.url.absoluteString }\n        try? process.run()\n    }\n\n    // TODO: allow custom file names\n    /// Action that creates a new untitled file\n    @objc\n    func newFile() {\n        guard let item else { return }\n        do {\n            if let newFile = try workspace?.workspaceFileManager?.addFile(fileName: \"untitled\", toFile: item) {\n                workspace?.listenerModel.highlightedFileItem = newFile\n                workspace?.editorManager?.openTab(item: newFile)\n            }\n        } catch {\n            let alert = NSAlert(error: error)\n            alert.addButton(withTitle: \"Dismiss\")\n            alert.runModal()\n        }\n    }\n\n    /// Opens the rename file dialogue on the cell this was presented from.\n    @objc\n    func renameFile() {\n        guard let newFile = workspace?.listenerModel.highlightedFileItem else { return }\n        let row = sender.outlineView.row(forItem: newFile)\n        guard row > 0,\n              let cell = sender.outlineView.view(\n                atColumn: 0,\n                row: row,\n                makeIfNecessary: false\n              ) as? ProjectNavigatorTableViewCell else {\n            return\n        }\n        sender.outlineView.window?.makeFirstResponder(cell.textField)\n    }\n\n    // TODO: Automatically identified the file type\n    /// Action that creates a new file with clipboard content\n    @objc\n    func newFileFromClipboard() {\n        guard let item else { return }\n        do {\n            let clipBoardContent = NSPasteboard.general.string(forType: .string)?.data(using: .utf8)\n            if let clipBoardContent, !clipBoardContent.isEmpty, let newFile = try workspace?\n                .workspaceFileManager?\n                .addFile(\n                    fileName: \"untitled\",\n                    toFile: item,\n                    contents: clipBoardContent\n                ) {\n                workspace?.listenerModel.highlightedFileItem = newFile\n                workspace?.editorManager?.openTab(item: newFile)\n                renameFile()\n            }\n        } catch {\n            let alert = NSAlert(error: error)\n            alert.addButton(withTitle: \"Dismiss\")\n            alert.runModal()\n        }\n    }\n\n    // TODO: allow custom folder names\n    /// Action that creates a new untitled folder\n    @objc\n    func newFolder() {\n        guard let item else { return }\n        do {\n            if let newFolder = try workspace?.workspaceFileManager?.addFolder(folderName: \"untitled\", toFile: item) {\n                workspace?.listenerModel.highlightedFileItem = newFolder\n            }\n        } catch {\n            let alert = NSAlert(error: error)\n            alert.addButton(withTitle: \"Dismiss\")\n            alert.runModal()\n        }\n    }\n\n    /// Creates a new folder with the items selected.\n    @objc\n    func newFolderFromSelection() {\n        guard let workspace, let workspaceFileManager = workspace.workspaceFileManager else { return }\n\n        let selectedItems = selectedItems()\n        guard let parent = selectedItems.first?.parent else { return }\n\n        /// Get 'New Folder' name.\n        var newFolderURL = parent.url.appendingPathComponent(\"New Folder With Items\", conformingTo: .folder)\n        var folderNumber = 0\n        while workspaceFileManager.fileManager.fileExists(atPath: newFolderURL.path) {\n            folderNumber += 1\n            newFolderURL = parent.url.appending(path: \"New Folder With Items \\(folderNumber)\")\n        }\n\n        do {\n            for selectedItem in selectedItems where selectedItem.url != newFolderURL {\n                try workspaceFileManager.move(file: selectedItem, to: newFolderURL.appending(path: selectedItem.name))\n            }\n        } catch {\n            let alert = NSAlert(error: error)\n            alert.addButton(withTitle: \"Dismiss\")\n            alert.runModal()\n        }\n\n        reloadData()\n    }\n\n    /// Action that moves the item to trash.\n    @objc\n    func trash() {\n        do {\n            try selectedItems().forEach { item in\n                withAnimation {\n                    sender.editor?.closeTab(file: item)\n                }\n                guard FileManager.default.fileExists(atPath: item.url.path) else {\n                    // Was likely already trashed (eg selecting files in a folder and deleting the folder and files)\n                    return\n                }\n                try workspace?.workspaceFileManager?.trash(file: item)\n            }\n            reloadData()\n        } catch {\n            let alert = NSAlert(error: error)\n            alert.addButton(withTitle: \"Dismiss\")\n            alert.runModal()\n        }\n    }\n\n    /// Action that deletes the item immediately.\n    @objc\n    func delete() {\n        do {\n            let selectedItems = selectedItems()\n            if selectedItems.count == 1 {\n                try selectedItems.forEach { item in\n                    try workspace?.workspaceFileManager?.delete(file: item)\n                }\n            } else {\n                try workspace?.workspaceFileManager?.batchDelete(files: selectedItems)\n            }\n\n            withAnimation {\n                selectedItems.forEach { item in\n                    sender.editor?.closeTab(file: item)\n                }\n            }\n\n            reloadData()\n        } catch {\n            let alert = NSAlert(error: error)\n            alert.addButton(withTitle: \"Dismiss\")\n            alert.runModal()\n        }\n    }\n\n    /// Action that duplicates the item\n    @objc\n    func duplicate() {\n        do {\n            try selectedItems().forEach { item in\n                try workspace?.workspaceFileManager?.duplicate(file: item)\n            }\n            reloadData()\n        } catch {\n            let alert = NSAlert(error: error)\n            alert.addButton(withTitle: \"Dismiss\")\n            alert.runModal()\n        }\n    }\n\n    /// Copies the absolute path of the selected files\n    @objc\n    func copyPath() {\n        let paths = selectedItems().map {\n            $0.url.standardizedFileURL.path\n        }.sorted().joined(separator: \"\\n\")\n        NSPasteboard.general.clearContents()\n        NSPasteboard.general.setString(paths, forType: .string)\n    }\n\n    /// Copies the relative path of the selected files\n    @objc\n    func copyRelativePath() {\n        guard let rootPath = workspace?.workspaceFileManager?.folderUrl else {\n            return\n        }\n        let paths = selectedItems().map {\n            let destinationComponents = $0.url.standardizedFileURL.pathComponents\n            let baseComponents = rootPath.standardizedFileURL.pathComponents\n\n            // Find common prefix length\n            var prefixCount = 0\n            while prefixCount < min(destinationComponents.count, baseComponents.count)\n                    && destinationComponents[prefixCount] == baseComponents[prefixCount] {\n                prefixCount += 1\n            }\n            // Build the relative path\n            let upPath = String(repeating: \"../\", count: baseComponents.count - prefixCount)\n            let downPath = destinationComponents[prefixCount...].joined(separator: \"/\")\n            return upPath + downPath\n        }.sorted().joined(separator: \"\\n\")\n\n        NSPasteboard.general.clearContents()\n        NSPasteboard.general.setString(paths, forType: .string)\n    }\n\n    private func reloadData() {\n        sender.outlineView.reloadData()\n        sender.filteredContentChildren.removeAll()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorNSOutlineView.swift",
    "content": "//\n//  ProjectNavigatorNSOutlineView.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 6/10/25.\n//\n\nimport AppKit\n\nfinal class ProjectNavigatorNSOutlineView: NSOutlineView, NSMenuItemValidation {\n    override func performKeyEquivalent(with event: NSEvent) -> Bool {\n        guard event.window === window && window?.firstResponder === self else {\n            return super.performKeyEquivalent(with: event)\n        }\n\n        if event.charactersIgnoringModifiers == \"v\"\n            && event.modifierFlags.intersection(.deviceIndependentFlagsMask) == .command {\n            guard let menu = menu as? ProjectNavigatorMenu else {\n                return super.performKeyEquivalent(with: event)\n            }\n            menu.delegate?.menuNeedsUpdate?(menu)\n            for fileItem in selectedRowIndexes.compactMap({ item(atRow: $0) as? CEWorkspaceFile }) {\n                menu.item = fileItem\n                menu.newFileFromClipboard()\n            }\n            return true\n        }\n        return super.performKeyEquivalent(with: event)\n    }\n\n    func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {\n        if menuItem.action == #selector(ProjectNavigatorMenu.newFileFromClipboard) {\n            return !selectedRowIndexes.isEmpty\n        }\n        return false\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorOutlineView.swift",
    "content": "//\n//  OutlineView.swift\n//  CodeEdit\n//\n//  Created by Lukas Pistrol on 05.04.22.\n//\n\nimport SwiftUI\nimport Combine\n\n/// Wraps an ``OutlineViewController`` inside a `NSViewControllerRepresentable`\nstruct ProjectNavigatorOutlineView: NSViewControllerRepresentable {\n\n    @EnvironmentObject var workspace: WorkspaceDocument\n    @EnvironmentObject var editorManager: EditorManager\n\n    @StateObject var prefs: Settings = .shared\n\n    typealias NSViewControllerType = ProjectNavigatorViewController\n\n    func makeNSViewController(context: Context) -> ProjectNavigatorViewController {\n        let controller = ProjectNavigatorViewController()\n        controller.workspace = workspace\n        controller.iconColor = prefs.preferences.general.fileIconStyle\n        controller.editor = editorManager.activeEditor\n        workspace.workspaceFileManager?.addObserver(context.coordinator)\n\n        context.coordinator.controller = controller\n\n        return controller\n    }\n\n    func updateNSViewController(_ nsViewController: ProjectNavigatorViewController, context: Context) {\n        nsViewController.iconColor = prefs.preferences.general.fileIconStyle\n        nsViewController.rowHeight = prefs.preferences.general.projectNavigatorSize.rowHeight\n        nsViewController.fileExtensionsVisibility = prefs.preferences.general.fileExtensionsVisibility\n        nsViewController.shownFileExtensions = prefs.preferences.general.shownFileExtensions\n        nsViewController.hiddenFileExtensions = prefs.preferences.general.hiddenFileExtensions\n        /// if the window becomes active from background, it will restore the selection to outline view.\n        nsViewController.updateSelection(itemID: workspace.editorManager?.activeEditor.selectedTab?.file.id)\n        return\n    }\n\n    func makeCoordinator() -> Coordinator {\n        Coordinator(workspace)\n    }\n\n    class Coordinator: NSObject, CEWorkspaceFileManagerObserver {\n        init(_ workspace: WorkspaceDocument) {\n            self.workspace = workspace\n            super.init()\n\n            workspace.listenerModel.$highlightedFileItem\n                .sink(receiveValue: { [weak self] fileItem in\n                    guard let fileItem else {\n                        return\n                    }\n                    self?.controller?.reveal(fileItem)\n                })\n                .store(in: &cancellables)\n            workspace.editorManager?.tabBarTabIdSubject\n                .sink { [weak self] editorInstance in\n                    self?.controller?.updateSelection(itemID: editorInstance?.file.id)\n                }\n                .store(in: &cancellables)\n            workspace.$navigatorFilter\n                .throttle(for: 0.1, scheduler: RunLoop.main, latest: true)\n                .sink { [weak self] _ in\n                    self?.controller?.handleFilterChange()\n                }\n                .store(in: &cancellables)\n            Publishers.Merge(workspace.$sourceControlFilter, workspace.$sortFoldersOnTop)\n                .throttle(for: 0.1, scheduler: RunLoop.main, latest: true)\n                .sink { [weak self] _ in\n                    self?.controller?.handleFilterChange()\n                }\n                .store(in: &cancellables)\n        }\n\n        var cancellables: Set<AnyCancellable> = []\n        weak var workspace: WorkspaceDocument?\n        weak var controller: ProjectNavigatorViewController?\n\n        func fileManagerUpdated(updatedItems: Set<CEWorkspaceFile>) {\n            guard let outlineView = controller?.outlineView else { return }\n            let selectedRows = outlineView.selectedRowIndexes.compactMap({ outlineView.item(atRow: $0) })\n\n            // If some text view inside the outline view is first responder right now, push the update off\n            // until editing is finished using the `shouldReloadAfterDoneEditing` flag.\n            if outlineView.window?.firstResponder !== outlineView\n                && outlineView.window?.firstResponder is NSTextView\n                && (outlineView.window?.firstResponder as? NSView)?.isDescendant(of: outlineView) == true {\n                controller?.shouldReloadAfterDoneEditing = true\n            } else {\n                for item in updatedItems {\n                    outlineView.reloadItem(item, reloadChildren: true)\n                }\n            }\n\n            // Restore selected items where the files still exist.\n            let selectedIndexes = selectedRows.compactMap({ outlineView.row(forItem: $0) }).filter({ $0 >= 0 })\n            controller?.shouldSendSelectionUpdate = false\n            outlineView.selectRowIndexes(IndexSet(selectedIndexes), byExtendingSelection: false)\n            controller?.shouldSendSelectionUpdate = true\n        }\n\n        deinit {\n            workspace?.workspaceFileManager?.removeObserver(self)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorTableViewCell.swift",
    "content": "//\n//  OutlineTableViewCell.swift\n//  CodeEdit\n//\n//  Created by Lukas Pistrol on 07.04.22.\n//\n\nimport SwiftUI\n\nprotocol OutlineTableViewCellDelegate: AnyObject {\n    func moveFile(file: CEWorkspaceFile, to destination: URL)\n    func copyFile(file: CEWorkspaceFile, to destination: URL)\n    func cellDidFinishEditing()\n}\n\n/// A `NSTableCellView` showing an ``icon`` and a ``label``\nfinal class ProjectNavigatorTableViewCell: FileSystemTableViewCell {\n    private weak var delegate: OutlineTableViewCellDelegate?\n\n    /// Initializes the `OutlineTableViewCell` with an `icon` and `label`\n    /// Both the icon and label will be colored, and sized based on the user's preferences.\n    /// - Parameters:\n    ///   - frameRect: The frame of the cell.\n    ///   - item: The file item the cell represents.\n    ///   - isEditable: Set to true if the user should be able to edit the file name.\n    ///   - navigatorFilter: An optional string use to filter the navigator area.\n    ///                      (Used for bolding and changing primary/secondary color).\n    init(\n        frame frameRect: NSRect,\n        item: CEWorkspaceFile?,\n        isEditable: Bool = true,\n        delegate: OutlineTableViewCellDelegate? = nil,\n        navigatorFilter: String? = nil\n    ) {\n        super.init(frame: frameRect, item: item, isEditable: isEditable, navigatorFilter: navigatorFilter)\n        self.textField?.setAccessibilityIdentifier(\"ProjectNavigatorTableViewCell-\\(item?.name ?? \"\")\")\n        self.delegate = delegate\n    }\n\n    /// *Not Implemented*\n    override init(frame frameRect: NSRect) {\n        super.init(frame: frameRect)\n        fatalError(\"\"\"\n        init(frame: ) isn't implemented on `OutlineTableViewCell`.\n        Please use `.init(frame: NSRect, item: WorkspaceClient.FileItem?)\n        \"\"\")\n    }\n\n    /// *Not Implemented*\n    required init?(coder: NSCoder) {\n        fatalError(\"\"\"\n        init?(coder: NSCoder) isn't implemented on `OutlineTableViewCell`.\n        Please use `.init(frame: NSRect, item: WorkspaceClient.FileItem?)\n        \"\"\")\n    }\n\n    override func controlTextDidEndEditing(_ obj: Notification) {\n        guard let fileItem else { return }\n        textField?.backgroundColor = fileItem.validateFileName(for: textField?.stringValue ?? \"\") ? .none : errorRed\n        if fileItem.validateFileName(for: textField?.stringValue ?? \"\") {\n            let destinationURL = fileItem.url\n                .deletingLastPathComponent()\n                .appending(path: textField?.stringValue ?? \"\")\n            delegate?.moveFile(file: fileItem, to: destinationURL)\n        } else {\n            textField?.stringValue = fileItem.labelFileName()\n        }\n        delegate?.cellDidFinishEditing()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSMenuDelegate.swift",
    "content": "//\n//  ProjectNavigatorViewController+NSMenuDelegate.swift\n//  CodeEdit\n//\n//  Created by Dscyre Scotti on 6/23/23.\n//\n\nimport SwiftUI\n\n// MARK: - NSMenuDelegate\nextension ProjectNavigatorViewController: NSMenuDelegate {\n\n    /// Once a menu gets requested by a `right click` setup the menu\n    ///\n    /// If the right click happened outside a row this will result in no menu being shown.\n    /// - Parameter menu: The menu that got requested\n    func menuNeedsUpdate(_ menu: NSMenu) {\n        let row = outlineView.clickedRow\n        guard let menu = menu as? ProjectNavigatorMenu else { return }\n\n        menu.workspace = workspace\n        if row == -1 {\n            menu.item = nil\n        } else {\n            if let item = outlineView.item(atRow: row) as? CEWorkspaceFile {\n                menu.item = item\n            } else {\n                menu.item = nil\n            }\n        }\n        menu.update()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSOutlineViewDataSource.swift",
    "content": "//\n//  ProjectNavigatorViewController+NSOutlineViewDataSource.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/13/24.\n//\n\nimport AppKit\n\nextension ProjectNavigatorViewController: NSOutlineViewDataSource {\n    /// Retrieves the children of a given item for the outline view, applying the current filter if necessary.\n    private func getOutlineViewItems(for item: CEWorkspaceFile) -> [CEWorkspaceFile] {\n        if let cachedChildren = filteredContentChildren[item] {\n            return cachedChildren\n                .sorted { lhs, rhs in\n                    workspace?.sortFoldersOnTop == true ? lhs.isFolder && !rhs.isFolder : lhs.name < rhs.name\n                }\n        }\n\n        if let workspace, let children = workspace.workspaceFileManager?.childrenOfFile(item) {\n            if !workspace.navigatorFilter.isEmpty || workspace.sourceControlFilter {\n                let filteredChildren = children.filter {\n                    fileSearchMatches(\n                        workspace.navigatorFilter,\n                        for: $0,\n                        sourceControlFilter: workspace.sourceControlFilter\n                    )\n                }\n\n                filteredContentChildren[item] = filteredChildren\n                return filteredChildren\n            }\n\n            return children\n                .sorted { lhs, rhs in\n                    workspace.sortFoldersOnTop ? lhs.isFolder && !rhs.isFolder : lhs.name < rhs.name\n                }\n        }\n\n        return []\n    }\n\n    func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {\n        if let item = item as? CEWorkspaceFile {\n            return getOutlineViewItems(for: item).count\n        }\n        return content.count\n    }\n\n    func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {\n        if let item = item as? CEWorkspaceFile {\n            return getOutlineViewItems(for: item)[index]\n        }\n        return content[index]\n    }\n\n    func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {\n        if let item = item as? CEWorkspaceFile {\n            return item.isFolder\n        }\n        return false\n    }\n\n    /// Write dragged file(s) to pasteboard\n    func outlineView(_ outlineView: NSOutlineView, pasteboardWriterForItem item: Any) -> NSPasteboardWriting? {\n        guard let fileItem = item as? CEWorkspaceFile else { return nil }\n        return fileItem.url as NSURL\n    }\n\n    /// Declare valid drop target\n    func outlineView(\n        _ outlineView: NSOutlineView,\n        validateDrop info: NSDraggingInfo,\n        proposedItem item: Any?,\n        proposedChildIndex index: Int\n    ) -> NSDragOperation {\n        guard let fileItem = item as? CEWorkspaceFile else { return [] }\n        // -1 index indicates that we are hovering over a row in outline view (folder or file)\n        if index == -1 {\n            if !fileItem.isFolder {\n                outlineView.setDropItem(fileItem.parent, dropChildIndex: index)\n            }\n            return info.draggingSourceOperationMask == .copy ? .copy : .move\n        }\n        return []\n    }\n\n    /// Handle successful or unsuccessful drop\n    func outlineView(\n        _ outlineView: NSOutlineView,\n        acceptDrop info: NSDraggingInfo,\n        item: Any?,\n        childIndex index: Int\n    ) -> Bool {\n        guard let pasteboardItems = info.draggingPasteboard.readObjects(forClasses: [NSURL.self]) else { return false }\n        let fileItemURLS = pasteboardItems.compactMap { $0 as? URL }\n\n        guard let fileItemDestination = item as? CEWorkspaceFile else { return false }\n        let destParentURL = fileItemDestination.url\n\n        for fileItemURL in fileItemURLS {\n            let destURL = destParentURL.appending(path: fileItemURL.lastPathComponent)\n            // cancel dropping file item on self or in parent directory\n            if fileItemURL == destURL || fileItemURL == destParentURL {\n                return false\n            }\n\n            // Needs to come before call to .removeItem or else race condition occurs\n            var srcFileItem: CEWorkspaceFile? = workspace?.workspaceFileManager?.getFile(fileItemURL.path)\n            // If srcFileItem is nil, fileItemUrl is an external file url.\n            if srcFileItem == nil {\n                srcFileItem = CEWorkspaceFile(url: URL(fileURLWithPath: fileItemURL.path))\n            }\n\n            guard let srcFileItem else {\n                return false\n            }\n\n            if CEWorkspaceFile.fileManager.fileExists(atPath: destURL.path) {\n                let shouldReplace = replaceFileDialog(fileName: fileItemURL.lastPathComponent)\n                guard shouldReplace else {\n                    return false\n                }\n                do {\n                    try CEWorkspaceFile.fileManager.removeItem(at: destURL)\n                } catch {\n                    fatalError(error.localizedDescription)\n                }\n            }\n            if info.draggingSourceOperationMask == .copy {\n                self.copyFile(file: srcFileItem, to: destURL)\n            } else {\n                self.moveFile(file: srcFileItem, to: destURL)\n            }\n        }\n        return true\n    }\n\n    func replaceFileDialog(fileName: String) -> Bool {\n        let alert = NSAlert()\n        alert.messageText = \"\"\"\n        A file or folder with the name \\(fileName) already exists in the destination folder. Do you want to replace it?\n        \"\"\"\n        alert.informativeText = \"This action is irreversible!\"\n        alert.alertStyle = .warning\n        alert.addButton(withTitle: \"Replace\")\n        alert.addButton(withTitle: \"Cancel\")\n        return alert.runModal() == .alertFirstButtonReturn\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSOutlineViewDelegate.swift",
    "content": "//\n//  ProjectNavigatorViewController+NSOutlineViewDelegate.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/13/24.\n//\n\nimport AppKit\n\nextension ProjectNavigatorViewController: NSOutlineViewDelegate {\n    func outlineView(\n        _ outlineView: NSOutlineView,\n        shouldShowCellExpansionFor tableColumn: NSTableColumn?,\n        item: Any\n    ) -> Bool {\n        true\n    }\n\n    func outlineView(_ outlineView: NSOutlineView, shouldShowOutlineCellForItem item: Any) -> Bool {\n        true\n    }\n\n    func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {\n        guard let tableColumn else { return nil }\n\n        let frameRect = NSRect(x: 0, y: 0, width: tableColumn.width, height: rowHeight)\n        let cell = ProjectNavigatorTableViewCell(\n            frame: frameRect,\n            item: item as? CEWorkspaceFile,\n            delegate: self,\n            navigatorFilter: workspace?.navigatorFilter\n        )\n        return cell\n    }\n\n    func outlineViewSelectionDidChange(_ notification: Notification) {\n        guard let outlineView = notification.object as? NSOutlineView else { return }\n\n        /// If multiple rows are selected, do not open any file.\n        guard outlineView.selectedRowIndexes.count == 1 else { return }\n\n        /// If only one row is selected, proceed as before\n        let selectedIndex = outlineView.selectedRow\n\n        guard let item = outlineView.item(atRow: selectedIndex) as? CEWorkspaceFile else { return }\n\n        if !item.isFolder && shouldSendSelectionUpdate {\n            shouldSendSelectionUpdate = false\n            if workspace?.editorManager?.activeEditor.selectedTab?.file != item {\n                workspace?.editorManager?.activeEditor.openTab(file: item, asTemporary: true)\n            }\n            shouldSendSelectionUpdate = true\n        }\n    }\n\n    func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat {\n        rowHeight // This can be changed to 20 to match Xcode's row height.\n    }\n\n    func outlineViewItemDidExpand(_ notification: Notification) {\n        /// Save expanded items' state to restore when finish filtering.\n        guard let workspace else { return }\n        if workspace.navigatorFilter.isEmpty, let item = notification.userInfo?[\"NSObject\"] as? CEWorkspaceFile {\n            expandedItems.insert(item)\n        }\n\n        guard let id = workspace.editorManager?.activeEditor.selectedTab?.file.id,\n              let item = workspace.workspaceFileManager?.getFile(id, createIfNotFound: true),\n              /// update outline selection only if the parent of selected item match with expanded item\n              item.parent === notification.userInfo?[\"NSObject\"] as? CEWorkspaceFile else {\n            return\n        }\n        /// select active file under collapsed folder only if its parent is expanding\n        if outlineView.isItemExpanded(item.parent) {\n            updateSelection(itemID: item.id)\n        }\n    }\n\n    func outlineViewItemDidCollapse(_ notification: Notification) {\n        /// Save expanded items' state to restore when finish filtering.\n        guard let workspace else { return }\n        if workspace.navigatorFilter.isEmpty, let item = notification.userInfo?[\"NSObject\"] as? CEWorkspaceFile {\n            expandedItems.remove(item)\n        }\n    }\n\n    func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any? {\n        guard let id = object as? CEWorkspaceFile.ID,\n              let item = workspace?.workspaceFileManager?.getFile(id, createIfNotFound: true) else { return nil }\n        return item\n    }\n\n    func outlineView(_ outlineView: NSOutlineView, persistentObjectForItem item: Any?) -> Any? {\n        guard let item = item as? CEWorkspaceFile else { return nil }\n        return item.id\n    }\n\n    /// Finds and selects an ``Item`` from an array of ``Item`` and their `children` based on the `id`.\n    /// - Parameters:\n    ///   - id: the id of the item item\n    ///   - collection: the array to search for\n    ///   - forcesReveal: The boolean to indicates whether or not it should force to reveal the selected file.\n    func select(by id: EditorTabID, forcesReveal: Bool) {\n        guard case .codeEditor(let path) = id,\n              let item = workspace?.workspaceFileManager?.getFile(path, createIfNotFound: true) else {\n            return\n        }\n        // If the user has set \"Reveal file on selection change\" to on or it is forced to reveal,\n        // we need to reveal the item before selecting the row.\n        if Settings.shared.preferences.general.revealFileOnFocusChange || forcesReveal {\n            reveal(item)\n        }\n        let row = outlineView.row(forItem: item)\n        if row == -1 {\n            outlineView.deselectRow(outlineView.selectedRow)\n        }\n        shouldSendSelectionUpdate = false\n        outlineView.selectRowIndexes(.init(integer: row), byExtendingSelection: false)\n        shouldSendSelectionUpdate = true\n    }\n\n    /// Reveals the given `fileItem` in the outline view by expanding all the parent directories of the file.\n    /// If the file is not found, it will present an alert saying so.\n    /// - Parameter fileItem: The file to reveal.\n    public func reveal(_ fileItem: CEWorkspaceFile) {\n        if let parent = fileItem.parent {\n            expandParent(item: parent)\n        }\n        let row = outlineView.row(forItem: fileItem)\n        shouldSendSelectionUpdate = false\n        outlineView.selectRowIndexes(.init(integer: row), byExtendingSelection: false)\n        shouldSendSelectionUpdate = true\n\n        if row < 0 {\n            let alert = NSAlert()\n            alert.messageText = NSLocalizedString(\n                \"Could not find file\",\n                comment: \"Could not find file\"\n            )\n            alert.runModal()\n            return\n        } else {\n            let visibleRect = scrollView.contentView.visibleRect\n            let visibleRows = outlineView.rows(in: visibleRect)\n            guard !visibleRows.contains(row) else {\n                /// in case that the selected file is not fully visible (some parts are out of the visible rect),\n                /// `scrollRowToVisible(_:)` method brings the file where it can be fully visible.\n                outlineView.scrollRowToVisible(row)\n                return\n            }\n            let rowRect = outlineView.rect(ofRow: row)\n            let centerY = rowRect.midY - (visibleRect.height / 2)\n            let center = NSPoint(x: 0, y: centerY)\n            /// `scroll(_:)` method alone doesn't bring the selected file to the center in some cases.\n            /// calling `scrollRowToVisible(_:)` method before it makes the file reveal in the center more correctly.\n            outlineView.scrollRowToVisible(row)\n            outlineView.scroll(center)\n        }\n    }\n\n    /// Method for recursively expanding a file's parent directories.\n    /// - Parameter item:\n    private func expandParent(item: CEWorkspaceFile) {\n        if let parent = item.parent as CEWorkspaceFile? {\n            expandParent(item: parent)\n        }\n        outlineView.expandItem(item)\n    }\n\n    /// Adds a tooltip to the file row.\n    func outlineView( // swiftlint:disable:this function_parameter_count\n        _ outlineView: NSOutlineView,\n        toolTipFor cell: NSCell,\n        rect: NSRectPointer,\n        tableColumn: NSTableColumn?,\n        item: Any,\n        mouseLocation: NSPoint\n    ) -> String {\n        if let file = item as? CEWorkspaceFile {\n            return file.name\n        }\n        return \"\"\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+OutlineTableViewCellDelegate.swift",
    "content": "//\n//  OutlintViewController+OutlineTableViewCellDelegate.swift\n//  CodeEdit\n//\n//  Created by Ziyuan Zhao on 2023/2/5.\n//\n\nimport Foundation\nimport AppKit\n\n// MARK: - OutlineTableViewCellDelegate\n\nextension ProjectNavigatorViewController: OutlineTableViewCellDelegate {\n    func moveFile(file: CEWorkspaceFile, to destination: URL) {\n        do {\n            guard let newFile = try workspace?.workspaceFileManager?.move(file: file, to: destination),\n                  !newFile.isFolder else {\n                return\n            }\n            outlineView.reloadItem(file.parent, reloadChildren: true)\n            if !file.isFolder {\n                workspace?.editorManager?.editorLayout.closeAllTabs(of: file)\n            }\n            workspace?.listenerModel.highlightedFileItem = newFile\n            workspace?.editorManager?.openTab(item: newFile)\n        } catch {\n            let alert = NSAlert(error: error)\n            alert.addButton(withTitle: \"Dismiss\")\n            alert.runModal()\n        }\n    }\n\n    func copyFile(file: CEWorkspaceFile, to destination: URL) {\n        do {\n            try workspace?.workspaceFileManager?.copy(file: file, to: destination)\n        } catch {\n            let alert = NSAlert(error: error)\n            alert.addButton(withTitle: \"Dismiss\")\n            alert.runModal()\n        }\n    }\n\n    func cellDidFinishEditing() {\n        guard shouldReloadAfterDoneEditing else { return }\n        outlineView.reloadData()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift",
    "content": "//\n//  OutlineViewController.swift\n//  CodeEdit\n//\n//  Created by Lukas Pistrol on 07.04.22.\n//\n\nimport AppKit\nimport SwiftUI\nimport OSLog\n\n/// A `NSViewController` that handles the **ProjectNavigatorView** in the **NavigatorArea**.\n///\n/// Adds a ``outlineView`` inside a ``scrollView`` which shows the folder structure of the\n/// currently open project.\nfinal class ProjectNavigatorViewController: NSViewController {\n    static let logger = Logger(\n        subsystem: Bundle.main.bundleIdentifier ?? \"\",\n        category: \"ProjectNavigatorViewController\"\n    )\n\n    var scrollView: NSScrollView!\n    var outlineView: NSOutlineView!\n    var noResultsLabel: NSTextField!\n\n    /// Gets the folder structure\n    ///\n    /// Also creates a top level item \"root\" which represents the projects root directory and automatically expands it.\n    var content: [CEWorkspaceFile] {\n        guard let folderURL = workspace?.workspaceFileManager?.folderUrl else { return [] }\n        guard let root = workspace?.workspaceFileManager?.getFile(folderURL.path) else { return [] }\n        return [root]\n    }\n\n    var filteredContentChildren: [CEWorkspaceFile: [CEWorkspaceFile]] = [:]\n    var expandedItems: Set<CEWorkspaceFile> = []\n\n    weak var workspace: WorkspaceDocument?\n    weak var editor: Editor?\n\n    var iconColor: SettingsData.FileIconStyle = .color {\n        willSet {\n            if newValue != iconColor {\n                outlineView?.reloadData()\n            }\n        }\n    }\n\n    var fileExtensionsVisibility: SettingsData.FileExtensionsVisibility = .showAll\n    var shownFileExtensions: SettingsData.FileExtensions = .default\n    var hiddenFileExtensions: SettingsData.FileExtensions = .default\n\n    var rowHeight: Double = 22 {\n        willSet {\n            if newValue != rowHeight {\n                outlineView.rowHeight = newValue\n                outlineView.reloadData()\n            }\n        }\n    }\n\n    /// This helps determine whether or not to send an `openTab` when the selection changes.\n    /// Used b/c the state may update when the selection changes, but we don't necessarily want\n    /// to open the file a second time.\n    var shouldSendSelectionUpdate: Bool = true\n\n    var shouldReloadAfterDoneEditing: Bool = false\n\n    var filterIsEmpty: Bool {\n        workspace?.navigatorFilter.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true\n    }\n\n    /// Setup the ``scrollView`` and ``outlineView``\n    override func loadView() {\n        self.scrollView = NSScrollView()\n        self.scrollView.hasVerticalScroller = true\n        self.view = scrollView\n\n        self.outlineView = ProjectNavigatorNSOutlineView()\n        self.outlineView.dataSource = self\n        self.outlineView.delegate = self\n        self.outlineView.autosaveExpandedItems = true\n        self.outlineView.autosaveName = workspace?.workspaceFileManager?.folderUrl.path ?? \"\"\n        self.outlineView.headerView = nil\n        self.outlineView.menu = ProjectNavigatorMenu(self)\n        self.outlineView.menu?.delegate = self\n        self.outlineView.doubleAction = #selector(onItemDoubleClicked)\n        self.outlineView.allowsMultipleSelection = true\n\n        self.outlineView.setAccessibilityIdentifier(\"ProjectNavigator\")\n        self.outlineView.setAccessibilityLabel(\"Project Navigator\")\n\n        let column = NSTableColumn(identifier: .init(rawValue: \"Cell\"))\n        column.title = \"Cell\"\n        outlineView.addTableColumn(column)\n\n        outlineView.setDraggingSourceOperationMask(.move, forLocal: false)\n        outlineView.registerForDraggedTypes([.fileURL])\n\n        scrollView.documentView = outlineView\n        scrollView.contentView.automaticallyAdjustsContentInsets = false\n        scrollView.contentView.contentInsets = .init(top: 10, left: 0, bottom: 0, right: 0)\n        scrollView.scrollerStyle = .overlay\n        scrollView.hasVerticalScroller = true\n        scrollView.hasHorizontalScroller = false\n        scrollView.autohidesScrollers = true\n\n        outlineView.expandItem(outlineView.item(atRow: 0))\n\n        /// Get autosave expanded items.\n        for row in 0..<outlineView.numberOfRows {\n            if let item = outlineView.item(atRow: row) as? CEWorkspaceFile {\n                if outlineView.isItemExpanded(item) {\n                    expandedItems.insert(item)\n                }\n            }\n        }\n\n        /// \"No Filter Results\" label.\n        noResultsLabel = NSTextField(labelWithString: \"No Filter Results\")\n        noResultsLabel.isHidden = true\n        noResultsLabel.font = NSFont.systemFont(ofSize: 16)\n        noResultsLabel.textColor = NSColor.secondaryLabelColor\n        outlineView.addSubview(noResultsLabel)\n        noResultsLabel.translatesAutoresizingMaskIntoConstraints = false\n        NSLayoutConstraint.activate([\n            noResultsLabel.centerXAnchor.constraint(equalTo: outlineView.centerXAnchor),\n            noResultsLabel.centerYAnchor.constraint(equalTo: outlineView.centerYAnchor)\n        ])\n    }\n\n    init() {\n        super.init(nibName: nil, bundle: nil)\n    }\n\n    deinit {\n        outlineView?.removeFromSuperview()\n        scrollView?.removeFromSuperview()\n        noResultsLabel?.removeFromSuperview()\n    }\n\n    required init?(coder: NSCoder) {\n        fatalError()\n    }\n\n    /// Forces to reveal the selected file through the command regardless of the auto reveal setting\n    @objc\n    func revealFile(_ sender: Any) {\n        updateSelection(itemID: workspace?.editorManager?.activeEditor.selectedTab?.file.id, forcesReveal: true)\n    }\n\n    /// Updates the selection of the ``outlineView`` whenever it changes.\n    ///\n    /// Most importantly when the `id` changes from an external view.\n    /// - Parameter itemID: The id of the file or folder.\n    /// - Parameter forcesReveal: The boolean to indicates whether or not it should force to reveal the selected file.\n    func updateSelection(itemID: String?, forcesReveal: Bool = false) {\n        guard let itemID else {\n            outlineView.deselectRow(outlineView.selectedRow)\n            return\n        }\n        self.select(by: .codeEditor(itemID), forcesReveal: forcesReveal)\n    }\n\n    /// Expand or collapse the folder on double click\n    @objc\n    private func onItemDoubleClicked() {\n        /// If there are multiples items selected, don't do anything, just like in Xcode.\n        guard outlineView.selectedRowIndexes.count == 1 else { return }\n\n        guard let item = outlineView.item(atRow: outlineView.clickedRow) as? CEWorkspaceFile else { return }\n\n        if item.isFolder {\n            if outlineView.isItemExpanded(item) {\n                outlineView.collapseItem(item)\n            } else {\n                outlineView.expandItem(item)\n            }\n        } else if Settings[\\.navigation].navigationStyle == .openInTabs {\n            workspace?.editorManager?.activeEditor.openTab(file: item, asTemporary: false)\n        }\n    }\n\n    /// Get the appropriate color for the items icon depending on the users preferences.\n    /// - Parameter item: The `FileItem` to get the color for\n    /// - Returns: A `NSColor` for the given `FileItem`.\n    private func color(for item: CEWorkspaceFile) -> NSColor {\n        if !item.isFolder && iconColor == .color {\n            return NSColor(item.iconColor)\n        } else {\n            return .secondaryLabelColor\n        }\n    }\n\n    func handleFilterChange() {\n        filteredContentChildren.removeAll()\n        outlineView.reloadData()\n\n        guard let workspace else { return }\n\n        /// If the filter is empty, show all items and restore the expanded state.\n        if workspace.sourceControlFilter || !filterIsEmpty {\n            outlineView.autosaveExpandedItems = false\n            /// Expand all items for search.\n            outlineView.expandItem(outlineView.item(atRow: 0), expandChildren: true)\n        } else {\n            restoreExpandedState()\n            outlineView.autosaveExpandedItems = true\n        }\n\n        if let root = content.first(where: { $0.isRoot }), let children = filteredContentChildren[root] {\n            if children.isEmpty {\n                noResultsLabel.isHidden = false\n                outlineView.hideRows(at: IndexSet(integer: 0))\n            } else {\n                noResultsLabel.isHidden = true\n            }\n        }\n    }\n\n    /// Checks if the given filter matches the name of the item or any of its children.\n    func fileSearchMatches(_ filter: String, for item: CEWorkspaceFile, sourceControlFilter: Bool) -> Bool {\n        guard !filterIsEmpty || sourceControlFilter else {\n            return true\n        }\n\n        if sourceControlFilter {\n            if item.gitStatus != nil && item.gitStatus != GitStatus.none &&\n                (filterIsEmpty || item.name.localizedCaseInsensitiveContains(filter)) {\n                saveAllContentChildren(for: item)\n                return true\n            }\n        } else if item.name.localizedCaseInsensitiveContains(filter) {\n            saveAllContentChildren(for: item)\n            return true\n        }\n\n        if let children = workspace?.workspaceFileManager?.childrenOfFile(item) {\n            return children.contains { fileSearchMatches(filter, for: $0, sourceControlFilter: sourceControlFilter) }\n        }\n\n        return false\n    }\n\n    /// Saves all children of a given folder item to the filtered content cache.\n    /// This is specially useful when the name of a folder matches the search.\n    /// Just like in Xcode, this shows all the content of the folder.\n    private func saveAllContentChildren(for item: CEWorkspaceFile) {\n        guard item.isFolder, filteredContentChildren[item] == nil else { return }\n\n        if let children = workspace?.workspaceFileManager?.childrenOfFile(item) {\n            filteredContentChildren[item] = children\n            for child in children.filter({ $0.isFolder }) {\n                saveAllContentChildren(for: child)\n            }\n        }\n    }\n\n    /// Restores the expanded state of items when finish searching.\n    private func restoreExpandedState() {\n        let copy = expandedItems\n        outlineView.collapseItem(outlineView.item(atRow: 0), collapseChildren: true)\n\n        for item in copy {\n            expandParentsRecursively(of: item)\n            outlineView.expandItem(item)\n        }\n\n        expandedItems = copy\n    }\n\n    /// Recursively expands all parent items of a given item in the outline view.\n    /// The order of the items may get lost in the `expandedItems` set.\n    /// This means that a children item might be expanded before its parent, causing it not to really expand.\n    private func expandParentsRecursively(of item: CEWorkspaceFile) {\n        if let parent = item.parent {\n            expandParentsRecursively(of: parent)\n            outlineView.expandItem(parent)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorToolbarBottom.swift",
    "content": "//\n//  ProjectNavigatorToolbarBottom.swift\n//  CodeEdit\n//\n//  Created by TAY KAI QUAN on 23/7/22.\n//\n\nimport SwiftUI\n\nstruct ProjectNavigatorToolbarBottom: View {\n    @Environment(\\.controlActiveState)\n    private var activeState\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @EnvironmentObject var workspace: WorkspaceDocument\n    @EnvironmentObject var editorManager: EditorManager\n\n    @State var recentsFilter: Bool = false\n\n    var body: some View {\n        HStack(spacing: 5) {\n            addNewFileButton\n            PaneTextField(\n                \"Filter\",\n                text: $workspace.navigatorFilter,\n                leadingAccessories: {\n                    FilterDropDownIconButton(menu: {\n                        ForEach([(true, \"Folders on top\"), (false, \"Alphabetically\")], id: \\.0) { value, title in\n                            Toggle(title, isOn: Binding(get: {\n                                workspace.sortFoldersOnTop == value\n                            }, set: { _ in\n                                // Avoid calling the handleFilterChange method\n                                if workspace.sortFoldersOnTop != value {\n                                    workspace.sortFoldersOnTop = value\n                                }\n                            }))\n                        }\n                    }, isOn: !workspace.navigatorFilter.isEmpty)\n                    .padding(.leading, 4)\n                    .foregroundStyle(\n                        workspace.navigatorFilter.isEmpty\n                        ? Color(nsColor: .secondaryLabelColor)\n                        : Color(nsColor: .controlAccentColor)\n                    )\n                    .help(\"Show files with matching name\")\n                },\n                trailingAccessories: {\n                    HStack(spacing: 0) {\n                        Toggle(isOn: $recentsFilter) {\n                            Image(systemName: \"clock\")\n                        }\n                        .help(\"Show only recent files\")\n                        Toggle(isOn: $workspace.sourceControlFilter) {\n                            Image(systemName: \"plusminus.circle\")\n                        }\n                        .help(\"Show only files with source-control status\")\n                    }\n                    .toggleStyle(.icon(font: .system(size: 14), size: CGSize(width: 18, height: 20)))\n                    .padding(.trailing, 2.5)\n                },\n                clearable: true,\n                hasValue: !workspace.navigatorFilter.isEmpty || recentsFilter || workspace.sourceControlFilter\n            )\n        }\n        .padding(.horizontal, 5)\n        .frame(height: 28, alignment: .center)\n        .frame(maxWidth: .infinity)\n        .overlay(alignment: .top) {\n            Divider()\n        }\n    }\n\n    /// Retrieves the active tab URL from the underlying editor instance, if theres no\n    /// active tab, fallbacks to the workspace's root directory\n    private func activeTabURL() -> URL {\n        if let selectedTab = editorManager.activeEditor.selectedTab {\n            if selectedTab.file.isFolder {\n                return selectedTab.file.url\n            }\n\n            // If the current active tab belongs to a file, pop the filename from\n            // the path URL to retrieve the folder URL\n            let activeTabFileURL = selectedTab.file.url\n\n            if URLComponents(url: activeTabFileURL, resolvingAgainstBaseURL: false) != nil {\n                var pathComponents = activeTabFileURL.pathComponents\n                pathComponents.removeLast()\n\n                let fileURL = NSURL.fileURL(withPathComponents: pathComponents)! as URL\n                return fileURL\n            }\n        }\n\n        return workspace.workspaceFileManager.unsafelyUnwrapped.folderUrl\n    }\n\n    private var addNewFileButton: some View {\n        Menu {\n            Button(\"Add File\") {\n                let filePathURL = activeTabURL()\n                guard let rootFile = workspace.workspaceFileManager?.getFile(filePathURL.path) else { return }\n                do {\n                    if let newFile = try workspace.workspaceFileManager?.addFile(\n                        fileName: \"untitled\",\n                        toFile: rootFile\n                    ) {\n                        workspace.listenerModel.highlightedFileItem = newFile\n                        workspace.editorManager?.openTab(item: newFile)\n                    }\n                } catch {\n                    let alert = NSAlert(error: error)\n                    alert.addButton(withTitle: \"Dismiss\")\n                    alert.runModal()\n                }\n            }\n\n            Button(\"Add Folder\") {\n                let filePathURL = activeTabURL()\n                guard let rootFile = workspace.workspaceFileManager?.getFile(filePathURL.path) else { return }\n                do {\n                    if let newFolder = try workspace.workspaceFileManager?.addFolder(\n                        folderName: \"untitled\",\n                        toFile: rootFile\n                    ) {\n                        workspace.listenerModel.highlightedFileItem = newFolder\n                    }\n                } catch {\n                    let alert = NSAlert(error: error)\n                    alert.addButton(withTitle: \"Dismiss\")\n                    alert.runModal()\n                }\n            }\n        } label: {}\n        .background {\n            Image(systemName: \"plus\")\n                .accessibilityHidden(true)\n        }\n        .menuStyle(.borderlessButton)\n        .menuIndicator(.hidden)\n        .frame(maxWidth: 18, alignment: .center)\n        .opacity(activeState == .inactive ? 0.45 : 1)\n        .accessibilityLabel(\"Add Folder or File\")\n        .accessibilityIdentifier(\"addButton\")\n    }\n\n    /// We clear the text and remove the first responder which removes the cursor\n    /// when the user clears the filter.\n    private var clearFilterButton: some View {\n        Button {\n            workspace.navigatorFilter = \"\"\n            NSApp.keyWindow?.makeFirstResponder(nil)\n        } label: {\n            Image(systemName: \"xmark.circle.fill\")\n                .symbolRenderingMode(.hierarchical)\n        }\n        .buttonStyle(.plain)\n        .opacity(activeState == .inactive ? 0.45 : 1)\n    }\n}\n\nstruct FilterDropDownIconButton<MenuView: View>: View {\n    @Environment(\\.controlActiveState)\n    private var activeState\n\n    var menu: () -> MenuView\n\n    var isOn: Bool?\n\n    var body: some View {\n        Menu { menu() } label: {}\n            .background {\n                if isOn == true {\n                    Image(ImageResource.line3HorizontalDecreaseChevronFilled)\n                        .foregroundStyle(.tint)\n                } else {\n                    Image(ImageResource.line3HorizontalDecreaseChevron)\n                }\n            }\n            .menuStyle(.borderlessButton)\n            .menuIndicator(.hidden)\n            .frame(width: 26, height: 13)\n            .clipShape(.rect(cornerRadius: 6.5))\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorView.swift",
    "content": "//\n//  ProjectNavigatorView.swift\n//  CodeEdit\n//\n//  Created by Lukas Pistrol on 25.03.22.\n//\n\nimport SwiftUI\n\n/// # Project Navigator - Sidebar\n///\n/// A list that functions as a project navigator, showing collapsible folders\n/// and files.\n///\n/// When selecting a file it will open in the editor.\n///\nstruct ProjectNavigatorView: View {\n    var body: some View {\n        ProjectNavigatorOutlineView()\n            .safeAreaInset(edge: .bottom, spacing: 0) {\n                ProjectNavigatorToolbarBottom()\n            }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorChangesCommitView.swift",
    "content": "//\n//  SourceControlNavigatorChangesCommitView.swift\n//  CodeEdit\n//\n//  Created by Albert Vinizhanau on 10/19/23.\n//\n\nimport SwiftUI\n\nstruct SourceControlNavigatorChangesCommitView: View {\n    @EnvironmentObject var sourceControlManager: SourceControlManager\n    @State private var message: String = \"\"\n    @State private var details: String = \"\"\n    @State private var ammend: Bool = false\n    @State private var showDetails: Bool = false\n    @State private var isCommiting: Bool = false\n\n    var allFilesStaged: Bool {\n        sourceControlManager.changedFiles.allSatisfy { $0.isStaged }\n    }\n\n    var anyFilesStaged: Bool {\n        sourceControlManager.changedFiles.contains { $0.isStaged }\n    }\n\n    var body: some View {\n        VStack(spacing: 0) {\n            VStack(spacing: 0) {\n                PaneTextField(\n                    \"Commit message (required)\",\n                    text: $message,\n                    axis: .vertical\n                )\n                .lineLimit(1...3)\n                .safeAreaInset(edge: .bottom, spacing: 0) {\n                    if showDetails {\n                        VStack {\n                            TextField(\n                                \"Detailed description\",\n                                text: $details,\n                                axis: .vertical\n                            )\n                            .textFieldStyle(.plain)\n                            .controlSize(.small)\n                            .lineLimit(3...5)\n\n                        }\n                        .frame(maxWidth: .infinity)\n                        .padding(.horizontal, 8)\n                        .padding(.vertical, 3.5)\n                        .overlay(alignment: .top) {\n                            VStack {\n                                Divider()\n                            }\n                        }\n                    }\n                }\n                VStack(spacing: 0) {\n                    if showDetails {\n                        Toggle(isOn: $ammend) {\n                            Text(\"Amend\")\n                                .frame(maxWidth: .infinity, alignment: .leading)\n                        }\n                        .toggleStyle(.switch)\n                        .controlSize(.mini)\n                        .padding(.top, 8)\n                        .transition(.move(edge: .top).combined(with: .opacity))\n                    }\n                }\n                .frame(maxWidth: .infinity)\n                .clipped()\n                HStack(spacing: 8) {\n                    Button {\n                        Task {\n                            if allFilesStaged {\n                                await resetAll()\n                            } else {\n                                await stageAll()\n                            }\n                        }\n                    } label: {\n                        Text(allFilesStaged ? \"Unstage All\" : \"Stage All\")\n                            .frame(maxWidth: .infinity)\n                    }\n                    Menu(isCommiting ? \"Committing...\" : \"Commit\") {\n                        Button(\"Commit and Push...\") {\n                            Task {\n                                self.isCommiting = true\n                                do {\n                                    try await sourceControlManager.commit(message: message, details: details)\n                                    self.message = \"\"\n                                    self.details = \"\"\n                                } catch {\n                                    await sourceControlManager.showAlertForError(\n                                        title: \"Failed to commit\",\n                                        error: error\n                                    )\n                                }\n                                do {\n                                    try await sourceControlManager.push()\n                                } catch {\n                                    await sourceControlManager.showAlertForError(title: \"Failed to push\", error: error)\n                                }\n                                self.isCommiting = false\n                            }\n                        }\n                    } primaryAction: {\n                        Task {\n                            self.isCommiting = true\n                            do {\n                                try await sourceControlManager.commit(message: message, details: details)\n                                self.message = \"\"\n                                self.details = \"\"\n                            } catch {\n                                await sourceControlManager.showAlertForError(title: \"Failed to commit\", error: error)\n                            }\n                            self.isCommiting = false\n                        }\n                    }\n                    .disabled(\n                        message.isEmpty ||\n                        !anyFilesStaged ||\n                        isCommiting\n                    )\n                }\n                .padding(.top, 8)\n            }\n            .transition(.move(edge: .top))\n            .onChange(of: message) { _, _ in\n                withAnimation(.easeInOut(duration: 0.25)) {\n                    showDetails = !message.isEmpty\n                }\n            }\n        }\n    }\n\n    /// Stages all changed files.\n    private func stageAll() async {\n        do {\n            try await sourceControlManager.add(sourceControlManager.changedFiles.compactMap {\n                $0.stagedStatus == .none ? $0.fileURL : nil\n            })\n        } catch {\n            sourceControlManager.logger.error(\"Failed to stage all files: \\(error)\")\n        }\n    }\n\n    /// Resets all changed files.\n    private func resetAll() async {\n        do {\n            try await sourceControlManager.reset(\n                sourceControlManager.changedFiles.map { $0.fileURL }\n            )\n        } catch {\n            sourceControlManager.logger.error(\"Failed to reset all files: \\(error)\")\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorChangesList.swift",
    "content": "//\n//  SourceControlNavigatorChangesList.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/18/23.\n//\n\nimport AppKit\nimport SwiftUI\n\nstruct SourceControlNavigatorChangesList: View {\n    @EnvironmentObject var workspace: WorkspaceDocument\n    @EnvironmentObject var sourceControlManager: SourceControlManager\n\n    @State var selection = Set<GitChangedFile>()\n\n    var body: some View {\n        List($sourceControlManager.changedFiles, selection: $selection) { $file in\n            GitChangedFileListView(changedFile: $file)\n                .listRowSeparator(.hidden)\n                .padding(.vertical, -1)\n                .tag($file.wrappedValue)\n        }\n        .environment(\\.defaultMinListRowHeight, 22)\n        .contextMenu(\n            forSelectionType: GitChangedFile.self,\n            menu: { selectedFiles in\n                if selectedFiles.count == 1,\n                   let file = selectedFiles.first {\n                    Group {\n                        Button(\"View in Finder\") {\n                            NSWorkspace.shared.activateFileViewerSelecting([file.fileURL.absoluteURL])\n                        }\n                        Button(\"Reveal in Project Navigator\") {}\n                            .disabled(true) // TODO: Implementation Needed\n                        Divider()\n                    }\n                    Group {\n                        Button(\"Open in New Tab\") {\n                            openGitFile(file)\n                        }\n                        Button(\"Open in New Window\") {}\n                            .disabled(true) // TODO: Implementation Needed\n                    }\n                    if file.anyStatus() != .none {\n                        Group {\n                            Divider()\n                            Button(\"Discard Changes in \\(file.fileURL.lastPathComponent)...\") {\n                                sourceControlManager.discardChanges(for: file.fileURL)\n                            }\n                            Divider()\n                        }\n                    }\n                } else {\n                    EmptyView()\n                }\n            },\n            // double-click action\n            primaryAction: { selectedFiles in\n                if selectedFiles.count == 1,\n                   let file = selectedFiles.first {\n                    openGitFile(file)\n                }\n            }\n        )\n        .onChange(of: selection) { _, newSelection in\n            if newSelection.count == 1,\n               let file = newSelection.first {\n                openGitFile(file)\n            }\n        }\n    }\n\n    private func openGitFile(_ file: GitChangedFile) {\n        guard let ceFile = workspace.workspaceFileManager?.getFile(file.ceFileKey, createIfNotFound: true) else {\n            return\n        }\n        DispatchQueue.main.async {\n            workspace.editorManager?.openTab(item: ceFile, asTemporary: true)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorChangesView.swift",
    "content": "//\n//  SourceControlNavigatorChangesView.swift\n//  CodeEdit\n//\n//  Created by Nanashi Li on 2022/05/20.\n//\n\nimport SwiftUI\n\nstruct SourceControlNavigatorChangesView: View {\n    @EnvironmentObject var sourceControlManager: SourceControlManager\n\n    var hasRemotes: Bool {\n        !sourceControlManager.remotes.isEmpty\n    }\n\n    var hasUnsyncedCommits: Bool {\n        sourceControlManager.numberOfUnsyncedCommits.ahead > 0\n        || sourceControlManager.numberOfUnsyncedCommits.behind > 0\n    }\n\n    var hasCurrentBranch: Bool {\n        (sourceControlManager.currentBranch != nil && sourceControlManager.currentBranch?.upstream == nil)\n    }\n\n    var hasChanges: Bool {\n        !sourceControlManager.changedFiles.isEmpty\n    }\n\n    var body: some View {\n        VStack(alignment: .center, spacing: 0) {\n            if hasChanges || !hasRemotes || (!hasChanges && (hasUnsyncedCommits || hasCurrentBranch)) {\n                VStack(spacing: 8) {\n                    Divided {\n                        if hasRemotes && (hasUnsyncedCommits || hasCurrentBranch) {\n                            SourceControlNavigatorSyncView(sourceControlManager: sourceControlManager)\n                        }\n                        if hasChanges {\n                            SourceControlNavigatorChangesCommitView()\n                        }\n                        if !hasRemotes {\n                            SourceControlNavigatorNoRemotesView()\n                        }\n                    }\n                }\n                .padding(.horizontal, 10)\n                .padding(.vertical, 8)\n                Divider()\n            }\n            if hasChanges {\n                SourceControlNavigatorChangesList()\n            } else {\n                CEContentUnavailableView(\"No Changes\")\n            }\n        }\n        .frame(maxHeight: .infinity)\n        .task {\n            await sourceControlManager.refreshAllChangedFiles()\n            await sourceControlManager.refreshNumberOfUnsyncedCommits()\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorNoRemotesView.swift",
    "content": "//\n//  SourceControlNavigatorNoRemotesView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/17/23.\n//\n\nimport SwiftUI\n\nstruct SourceControlNavigatorNoRemotesView: View {\n    @EnvironmentObject var sourceControlManager: SourceControlManager\n\n    var body: some View {\n        VStack(spacing: 0) {\n            HStack {\n                Label(\n                    title: {\n                        Text(\"No remotes\")\n                    }, icon: {\n                        Image(systemName: \"network\")\n                            .foregroundColor(.secondary)\n                    }\n                )\n                Spacer()\n                Button(\"Add\") {\n                    sourceControlManager.addExistingRemoteSheetIsPresented = true\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorSyncView.swift",
    "content": "//\n//  SourceControlNavigatorSyncView.swift\n//  CodeEdit\n//\n//  Created by Albert Vinizhanau on 10/20/23.\n//\n\nimport SwiftUI\n\nstruct SourceControlNavigatorSyncView: View {\n    @ObservedObject var sourceControlManager: SourceControlManager\n    @State private var isLoading: Bool = false\n\n    var body: some View {\n        if let currentBranch = sourceControlManager.currentBranch {\n            HStack {\n                if currentBranch.upstream == nil {\n                    Label(title: {\n                        Text(\"No tracked branch for '\\(sourceControlManager.currentBranch?.name ?? \"\")'\")\n                    }, icon: {\n                        Image(symbol: \"branch\")\n                            .foregroundStyle(.secondary)\n                    })\n                } else {\n                    Label(title: {\n                        Text(\n                            formatUnsyncedlabel(\n                                ahead: sourceControlManager.numberOfUnsyncedCommits.ahead,\n                                behind: sourceControlManager.numberOfUnsyncedCommits.behind\n                            )\n                        )\n                    }, icon: {\n                        Image(systemName: \"arrow.up.arrow.down\")\n                            .foregroundStyle(.secondary)\n                    })\n                }\n\n                Spacer()\n                if sourceControlManager.numberOfUnsyncedCommits.behind > 0 {\n                    Button {\n                        sourceControlManager.pullSheetIsPresented = true\n                    } label: {\n                        Text(\"Pull...\")\n                    }\n                    .disabled(isLoading)\n                } else if sourceControlManager.numberOfUnsyncedCommits.ahead > 0\n                    || currentBranch.upstream == nil {\n                    Button {\n                        sourceControlManager.pushSheetIsPresented = true\n                    } label: {\n                        Text(\"Push...\")\n                    }\n                    .disabled(isLoading)\n                }\n            }\n        }\n    }\n\n    func pull() {\n        Task(priority: .background) {\n            self.isLoading = true\n            do {\n                try await sourceControlManager.pull()\n            } catch {\n                await sourceControlManager.showAlertForError(title: \"Failed to pull\", error: error)\n            }\n            self.isLoading = false\n        }\n    }\n\n    func push() {\n        Task(priority: .background) {\n            self.isLoading = true\n            do {\n                try await sourceControlManager.push()\n            } catch {\n                await sourceControlManager.showAlertForError(title: \"Failed to push\", error: error)\n            }\n            self.isLoading = false\n        }\n    }\n\n    func formatUnsyncedlabel(ahead: Int?, behind: Int?) -> String {\n        var parts: [String] = []\n\n        if let ahead = ahead, ahead > 0 {\n            parts.append(\"\\(ahead) ahead\")\n        }\n\n        if let behind = behind, behind > 0 {\n            parts.append(\"\\(behind) behind\")\n        }\n\n        return parts.joined(separator: \", \")\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/SourceControlNavigator/History/Views/CommitDetailsHeaderView.swift",
    "content": "//\n//  CommitDetailsHeaderView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 12/27/23.\n//\n\nimport SwiftUI\n\nstruct CommitDetailsHeaderView: View {\n    var commit: GitCommit\n\n    private var defaultAvatar: some View {\n        Image(systemName: \"person.crop.circle.fill\")\n            .symbolRenderingMode(.hierarchical)\n            .resizable()\n            .foregroundColor(avatarColor)\n            .frame(width: 32, height: 32)\n    }\n\n    private func commitDetails() -> String {\n        if commit.committerEmail == \"noreply@github.com\" {\n            return commit.message.trimmingCharacters(in: .whitespacesAndNewlines)\n        } else if commit.authorEmail != commit.committerEmail {\n            return commit.message.trimmingCharacters(in: .whitespacesAndNewlines)\n        } else {\n            return \"\\(commit.message)\\n\\n\\(coAuthDetail())\".trimmingCharacters(in: .whitespacesAndNewlines)\n        }\n    }\n\n    private func coAuthDetail() -> String {\n        if commit.committerEmail == \"noreply@github.com\" {\n            return \"\"\n        } else if commit.authorEmail != commit.committerEmail {\n            return \"Co-authored by: \\(commit.committer)\\n<\\(commit.committerEmail)>\"\n        }\n        return \"\"\n    }\n\n    private func generateAvatarHash() -> String {\n        let hash = commit.authorEmail.md5(trim: true, caseSensitive: false)\n        return \"\\(hash)?d=404&s=64\" // send 404 if no image available, image size 64x64 (32x32 @2x)\n    }\n\n    private var avatarColor: Color {\n        let hash = generateAvatarHash().hash\n        switch hash % 12 {\n        case 0: return .red\n        case 1: return .orange\n        case 2: return .yellow\n        case 3: return .green\n        case 4: return .mint\n        case 5: return .teal\n        case 6: return .cyan\n        case 7: return .blue\n        case 8: return .indigo\n        case 9: return .purple\n        case 10: return .brown\n        case 11: return .pink\n        default: return .teal\n        }\n    }\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 10) {\n            HStack(alignment: .top) {\n                AsyncImage(url: URL(string: \"https://www.gravatar.com/avatar/\\(generateAvatarHash())\")) { phase in\n                    if let image = phase.image {\n                        image\n                            .resizable()\n                            .clipShape(Circle())\n                            .frame(width: 32, height: 32)\n                            .help(commit.author)\n                    } else if phase.error != nil {\n                        defaultAvatar\n                            .help(commit.author)\n                    } else {\n                        defaultAvatar\n                            .help(commit.author)\n                    }\n                }\n\n                VStack(alignment: .leading) {\n                    Text(commit.author)\n                        .fontWeight(.bold)\n                    Text(commit.date.formatted(date: .abbreviated, time: .shortened))\n                        .font(.subheadline)\n                        .foregroundStyle(.secondary)\n                }\n\n                Spacer()\n\n                Text(commit.hash)\n                    .font(.subheadline)\n                    .fontDesign(.monospaced)\n                    .background(\n                        RoundedRectangle(cornerRadius: 3)\n                            .padding(.horizontal, -2.5)\n                            .padding(.vertical, -1)\n                            .foregroundColor(Color(nsColor: .quaternaryLabelColor))\n                    )\n                    .padding(.horizontal, 2.5)\n            }\n            .padding(.horizontal, 16)\n\n            Divider()\n\n            Text(commitDetails())\n                .fontWeight(.bold)\n                .padding(.horizontal, 16)\n                .frame(alignment: .leading)\n\n            if !commit.body.isEmpty {\n                Text(commit.body)\n                    .padding(.horizontal, 16)\n                    .frame(alignment: .leading)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/SourceControlNavigator/History/Views/CommitDetailsView.swift",
    "content": "//\n//  CommitDetailsView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 12/27/23.\n//\n\nimport SwiftUI\n\nstruct CommitDetailsView: View {\n    @EnvironmentObject var sourceControlManager: SourceControlManager\n\n    @Binding var commit: GitCommit?\n\n    @State var commitChanges: [GitChangedFile] = []\n\n    @State var selection: CEWorkspaceFile?\n\n    func updateCommitChanges() async throws {\n        if let commit = commit {\n            let changes = await sourceControlManager\n                .getCommitChangedFiles(commitSHA: commit.commitHash)\n            commitChanges = changes\n        }\n    }\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 0) {\n            HStack {\n                Button {\n                    commit = nil\n                } label: {\n                    Image(systemName: \"chevron.backward\")\n                }\n                .buttonStyle(SidebarButtonStyle())\n                Text(\"Commit Details\")\n                    .font(.system(size: 13, weight: .bold))\n            }\n            .padding(10)\n            Divider()\n\n            if let commit = commit {\n                CommitDetailsHeaderView(commit: commit)\n                    .padding(.vertical, 16)\n                Divider()\n\n                if !commitChanges.isEmpty {\n                    List(selection: $selection) {\n                        ForEach($commitChanges, id: \\.self) { $file in\n                            GitChangedFileListView(changedFile: $file, showStaged: false)\n                                .fixedSize(horizontal: false, vertical: true)\n                                .listRowSeparator(.hidden)\n                                .padding(.vertical, -1)\n                        }\n\n                    }\n                    .environment(\\.defaultMinListRowHeight, 22)\n                } else {\n                    CEContentUnavailableView(\"No Changes\")\n                }\n            } else {\n                Spacer()\n            }\n        }\n        .onAppear {\n            Task {\n                try await updateCommitChanges()\n            }\n        }\n    }\n}\n\nstruct SidebarButtonStyle: ButtonStyle {\n    var isActive: Bool = false\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @Environment(\\.controlActiveState)\n    private var activeState\n\n    @State var isHovering: Bool = false\n\n    private var textOpacity: Double {\n        return activeState != .inactive ? 1 : 0.3\n    }\n\n    func makeBody(configuration: Self.Configuration) -> some View {\n        configuration.label\n            .font(.body)\n            .foregroundColor(configuration.isPressed ? .primary : .secondary)\n            .opacity(textOpacity)\n            .frame(height: 20)\n            .padding(.horizontal, 5)\n            .background(\n                RoundedRectangle(cornerSize: CGSize(width: 5, height: 5))\n                    .strokeBorder(.separator, lineWidth: 1)\n                    .background(\n                        RoundedRectangle(cornerSize: CGSize(width: 4, height: 4))\n                            .fill(\n                                Color(nsColor: colorScheme == .dark ? .white : .black)\n                                    .opacity(configuration.isPressed ? 0.10 : isHovering ? 0.05 : 0)\n                            )\n                            .padding(1)\n                    )\n                    .opacity(activeState != .inactive ? 1 : 0.3)\n\n            )\n            .onHover { hover in\n                isHovering = hover\n            }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/SourceControlNavigator/History/Views/CommitListItemView.swift",
    "content": "//\n//  CommitListItemView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 12/27/2023.\n//\n\nimport SwiftUI\n\nstruct CommitListItemView: View {\n\n    var commit: GitCommit\n    var showRef: Bool\n    var width: CGFloat\n\n    private var defaultAvatar: some View {\n        Image(systemName: \"person.crop.circle.fill\")\n            .symbolRenderingMode(.hierarchical)\n            .resizable()\n            .foregroundColor(avatarColor)\n            .frame(width: 32, height: 32)\n    }\n\n    private func generateAvatarHash() -> String {\n        let hash = commit.authorEmail.md5(trim: true, caseSensitive: false)\n        return \"\\(hash)?d=404&s=64\" // send 404 if no image available, image size 64x64 (32x32 @2x)\n    }\n\n    private var avatarColor: Color {\n        let hash = generateAvatarHash().hash\n        switch hash % 12 {\n        case 0: return .red\n        case 1: return .orange\n        case 2: return .yellow\n        case 3: return .green\n        case 4: return .mint\n        case 5: return .teal\n        case 6: return .cyan\n        case 7: return .blue\n        case 8: return .indigo\n        case 9: return .purple\n        case 10: return .brown\n        case 11: return .pink\n        default: return .teal\n        }\n    }\n\n    @Environment(\\.openURL)\n    private var openCommit\n\n    init(commit: GitCommit, showRef: Bool) {\n        self.commit = commit\n        self.showRef = showRef\n        self.width = 0\n    }\n\n    init(commit: GitCommit, showRef: Bool, width: CGFloat) {\n        self.commit = commit\n        self.showRef = showRef\n        self.width = width\n    }\n\n    var body: some View {\n        HStack(alignment: .top) {\n            if width > 360 {\n                AsyncImage(url: URL(string: \"https://www.gravatar.com/avatar/\\(generateAvatarHash())\")) { phase in\n                    if let image = phase.image {\n                        image\n                            .resizable()\n                            .clipShape(Circle())\n                            .frame(width: 32, height: 32)\n                            .help(commit.author)\n                    } else if phase.error != nil {\n                        defaultAvatar\n                            .help(commit.author)\n                    } else {\n                        defaultAvatar\n                            .help(commit.author)\n                    }\n                }\n            }\n            VStack(alignment: .leading, spacing: 0) {\n                HStack {\n                    Text(commit.author)\n                        .fontWeight(.bold)\n                        .font(.system(size: 11))\n                    if showRef {\n                        if !commit.refs.isEmpty {\n                            HStack {\n                                ForEach(commit.refs, id: \\.self) { ref in\n                                    HStack(spacing: 2.5) {\n                                        Image.branch\n                                            .imageScale(.small)\n                                            .foregroundColor(.secondary)\n                                            .help(ref)\n                                        Text(ref)\n                                    }\n                                    .font(.system(size: 10))\n                                    .frame(height: 13)\n                                    .background(\n                                        RoundedRectangle(cornerRadius: 3)\n                                            .padding(.vertical, -1)\n                                            .padding(.leading, -2.5)\n                                            .padding(.trailing, -4)\n                                            .foregroundColor(Color(nsColor: .quaternaryLabelColor))\n                                    )\n                                    .padding(.trailing, 2.5)\n                                }\n                            }\n                        }\n\n                        if !commit.tag.isEmpty {\n                            HStack(spacing: 2.5) {\n                                Image(systemName: \"tag\")\n                                    .imageScale(.small)\n                                    .foregroundColor(.primary)\n                                    .help(commit.tag)\n                                Text(commit.tag)\n                            }\n                            .font(.system(size: 10))\n                            .frame(height: 13)\n                            .background(\n                                RoundedRectangle(cornerRadius: 3)\n                                    .padding(.vertical, -1)\n                                    .padding(.leading, -2.5)\n                                    .padding(.trailing, -4)\n                                    .foregroundColor(Color(nsColor: .purple).opacity(0.2))\n                            )\n                            .padding(.trailing, 2.5)\n                        }\n                    }\n                }\n\n                Text(\"\\(commit.message) \\(commit.body)\")\n                    .font(.system(size: 11))\n                    .lineLimit(2)\n            }\n            Spacer()\n            VStack(alignment: .trailing, spacing: 5) {\n                Text(commit.hash)\n                    .font(.system(size: 10, design: .monospaced))\n                    .background(\n                        RoundedRectangle(cornerRadius: 3)\n                            .padding(.vertical, -1)\n                            .padding(.horizontal, -2.5)\n                            .foregroundColor(Color(nsColor: .quaternaryLabelColor))\n                    )\n                    .padding(.trailing, 2.5)\n                Text(commit.date.relativeStringToNow())\n                    .font(.system(size: 11))\n                    .foregroundColor(.secondary)\n            }\n            .padding(.top, 1)\n        }\n        .padding(.vertical, 1)\n        .contentShape(Rectangle())\n        .contextMenu {\n            Group {\n                Button(\"Copy Commit Message\") {\n                    let pasteboard = NSPasteboard.general\n                    pasteboard.clearContents()\n                    pasteboard.setString(commit.message, forType: .string)\n                }\n                Button(\"Copy Identifier\") {\n                    let pasteboard = NSPasteboard.general\n                    pasteboard.clearContents()\n                    pasteboard.setString(commit.commitHash, forType: .string)\n                }\n                Button(\"Email \\(commit.author)...\") {\n                    let service = NSSharingService(named: NSSharingService.Name.composeEmail)\n                    service?.recipients = [commit.authorEmail]\n                    service?.perform(withItems: [])\n                }\n                Divider()\n            }\n            Group {\n                Button(\"Tag \\(commit.hash)...\") {}\n                    .disabled(true) // TODO: Implementation Needed\n                Button(\"New Branch from \\(commit.hash)...\") {}\n                    .disabled(true) // TODO: Implementation Needed\n                Button(\"Cherry-Pick \\(commit.hash)...\") {}\n                    .disabled(true) // TODO: Implementation Needed\n            }\n            Group {\n                Divider()\n                if let commitRemoteURL = commit.commitBaseURL?.absoluteString {\n                    Button(\"View on \\(commit.remoteString)...\") {\n                        let commitURL = \"\\(commitRemoteURL)/\\(commit.commitHash)\"\n                        openCommit(URL(string: commitURL)!)\n                    }\n                    Divider()\n                }\n                Button(\"Check Out \\(commit.hash)...\") {}\n                    .disabled(true) // TODO: Implementation Needed\n                Divider()\n                Button(\"History Editor Help\") {}\n                    .disabled(true) // TODO: Implementation Needed\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/SourceControlNavigator/History/Views/SourceControlNavigatorHistoryView.swift",
    "content": "//\n//  SourceControlNavigatorHistoryView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 12/27/2023.\n//\n\nimport SwiftUI\nimport CodeEditSymbols\n\nstruct SourceControlNavigatorHistoryView: View {\n    enum Status {\n        case loading\n        case ready\n        case error(error: Error)\n    }\n\n    @AppSettings(\\.sourceControl.git.showMergeCommitsPerFileLog)\n    var showMergeCommitsPerFileLog\n\n    @EnvironmentObject var sourceControlManager: SourceControlManager\n\n    @State var commitHistoryStatus: Status = .loading\n    @State var commitHistory: [GitCommit] = []\n\n    @State var selection: GitCommit?\n    @State private var width: CGFloat = CGFloat.zero\n\n    func updateCommitHistory() async {\n        do {\n            commitHistoryStatus = .loading\n            let commits = try await sourceControlManager\n                .gitClient\n                .getCommitHistory(\n                    branchName: sourceControlManager.currentBranch?.name,\n                    showMergeCommits: Settings.shared.preferences.sourceControl.git.showMergeCommitsPerFileLog\n                )\n            await MainActor.run {\n                commitHistory = commits\n                commitHistoryStatus = .ready\n            }\n        } catch {\n            sourceControlManager.logger.log(\"Failed to load commit history: \\(error)\")\n            await MainActor.run {\n                commitHistory = []\n                commitHistoryStatus = .error(error: error)\n            }\n        }\n    }\n\n    var body: some View {\n        Group {\n            switch commitHistoryStatus {\n            case .loading:\n                VStack {\n                    Spacer()\n                    ProgressView {\n                        Text(\"Loading History\")\n                    }\n                    Spacer()\n                }\n            case .ready:\n                if commitHistory.isEmpty {\n                    CEContentUnavailableView(\"No History\")\n                } else {\n                    GeometryReader { geometry in\n                        ZStack {\n                            List(selection: $selection) {\n                                ForEach(commitHistory) { commit in\n                                    CommitListItemView(commit: commit, showRef: true, width: width)\n                                        .tag(commit)\n                                        .listRowSeparator(.hidden)\n                                }\n                            }\n                            .opacity(selection == nil ? 1 : 0)\n                            if selection != nil {\n                                CommitDetailsView(commit: $selection)\n                            }\n                        }\n                        .onAppear {\n                            self.width = geometry.size.width\n                        }\n                        .onChange(of: geometry.size.width) { _, newWidth in\n                            self.width = newWidth\n                        }\n                    }\n                }\n            case .error(let error):\n                VStack {\n                    Spacer()\n                    CEContentUnavailableView(\n                        \"Error Loading History\",\n                        description: error.localizedDescription,\n                        systemImage: \"exclamationmark.triangle\"\n                    ) {\n                        Button {\n                            Task {\n                                await updateCommitHistory()\n                            }\n                        } label: {\n                            Text(\"Retry\")\n                        }\n                    }\n                    Spacer()\n                }\n            }\n        }\n        .task {\n            await updateCommitHistory()\n        }\n        .onChange(of: showMergeCommitsPerFileLog) { _, _ in\n            Task {\n                await updateCommitHistory()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Models/RepoOutlineGroupItem.swift",
    "content": "//\n//  RepoOutlineGroupItem.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/29/23.\n//\n\nimport SwiftUI\n\nstruct RepoOutlineGroupItem: Hashable, Identifiable {\n    enum ImageType: Hashable {\n        case system(name: String)\n        case symbol(name: String)\n    }\n\n    var id: String\n    var label: String\n    var description: String?\n    var image: ImageType\n    var imageColor: Color\n    var children: [RepoOutlineGroupItem]?\n    var branch: GitBranch?\n    var stashEntry: GitStashEntry?\n    var remote: GitRemote?\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryItem.swift",
    "content": "//\n//  SourceControlNavigatorRepositoriesItem.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/29/23.\n//\n\nimport SwiftUI\n\nstruct SourceControlNavigatorRepositoryItem: View {\n    @AppSettings(\\.general.fileIconStyle)\n    var fileIconStyle\n\n    let item: RepoOutlineGroupItem\n\n    @Environment(\\.controlActiveState)\n    var controlActiveState\n\n    var body: some View {\n        Label(title: {\n            Text(item.label)\n                .lineLimit(1)\n                .truncationMode(.middle)\n            if let description = item.description {\n                Text(description)\n                    .lineLimit(1)\n                    .foregroundStyle(.secondary)\n                    .font(.system(size: 11))\n                    .layoutPriority(-1)\n            }\n            Spacer()\n            HStack(spacing: 5) {\n                if let behind = item.branch?.behind, behind > 0 {\n                    HStack(spacing: 0) {\n                        Image(systemName: \"arrow.down\")\n                            .imageScale(.small)\n                        Text(\"\\(behind)\")\n                            .font(.system(size: 11))\n                    }\n                }\n                if let ahead = item.branch?.ahead, ahead > 0 {\n                    HStack(spacing: 0) {\n                        Image(systemName: \"arrow.up\")\n                            .imageScale(.small)\n                        Text(\"\\(ahead)\")\n                            .font(.system(size: 11))\n                    }\n                }\n            }\n        }, icon: {\n            Group {\n                switch item.image {\n                case .system(let name):\n                    Image(systemName: name)\n                case .symbol(let name):\n                    Image(symbol: name)\n                }\n            }\n            .opacity(controlActiveState == .inactive ? 0.5 : 1)\n            .foregroundStyle(fileIconStyle == .color ? item.imageColor : Color.coolGray)\n        })\n        .padding(.leading, 1)\n        .padding(.vertical, -1)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryView+contextMenu.swift",
    "content": "//\n//  SourceControlNavigatorRepositoriesView+contextMenu.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/29/23.\n//\n\nimport SwiftUI\n\nextension SourceControlNavigatorRepositoryView {\n    func handleDelete(_ item: RepoOutlineGroupItem) {\n        if item.branch != nil {\n            isPresentingConfirmDeleteBranch = true\n            branchToDelete = item.branch\n        }\n        if item.stashEntry != nil {\n            isPresentingConfirmDeleteStashEntry = true\n            stashEntryToDelete = item.stashEntry\n        }\n        if item.remote != nil {\n            isPresentingConfirmDeleteRemote = true\n            remoteToDelete = item.remote\n        }\n    }\n\n    @ViewBuilder\n    func contextMenu(for item: RepoOutlineGroupItem, branch: GitBranch) -> some View {\n        Button(\"Switch...\") {\n            sourceControlManager.switchToBranch = branch\n        }\n        .disabled(item.branch == nil || sourceControlManager.currentBranch == item.branch)\n        Divider()\n        Button(\n            item.branch == nil && item.id != \"BranchesGroup\"\n            ? \"New Branch...\"\n            : \"New Branch from \\\"\\(branch.name)\\\"...\"\n        ) {\n            showNewBranch = true\n            fromBranch =  item.branch\n        }\n        .disabled(item.branch == nil && item.id != \"BranchesGroup\")\n        Button(\n            item.branch == nil\n            ? \"Rename Branch...\"\n            : \"Rename \\\"\\(branch.name)\\\"...\"\n        ) {\n            showRenameBranch = true\n            fromBranch = item.branch\n        }\n        .disabled(item.branch == nil || item.branch?.isRemote == true)\n        Divider()\n        Button(\"Add Existing Remote...\") {\n            sourceControlManager.addExistingRemoteSheetIsPresented = true\n        }\n        .disabled(item.id != \"RemotesGroup\")\n        Divider()\n        Button(\"Apply Stashed Changes...\") {\n            applyStashedChangesIsPresented = true\n            stashEntryToApply = item.stashEntry\n        }\n        .disabled(item.stashEntry == nil)\n        Divider()\n        Button(\"Delete...\") {\n            handleDelete(item)\n        }\n        .disabled(\n            (item.branch == nil\n             || item.branch?.isLocal == false\n             || sourceControlManager.currentBranch == item.branch)\n            && item.stashEntry == nil\n            && item.remote == nil\n        )\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryView+outlineGroupData.swift",
    "content": "//\n//  SourceControlNavigatorRepositoriesView+outlineGroupData.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/29/23.\n//\n\nimport SwiftUI\n\nextension SourceControlNavigatorRepositoryView {\n    var outlineGroupData: [RepoOutlineGroupItem] {\n        [\n            .init(\n                id: \"BranchesGroup\",\n                label: \"Branches\",\n                image: .system(name: \"externaldrive.fill\"),\n                imageColor: Color(nsColor: .secondaryLabelColor),\n                children: sourceControlManager.orderedLocalBranches.map { branch in\n                        .init(\n                            id: \"Branch\\(branch.name)\",\n                            label: branch.name,\n                            description: branch == sourceControlManager.currentBranch ? \"(current)\" : nil,\n                            image: .symbol(name: \"branch\"),\n                            imageColor: .blue,\n                            branch: branch\n                        )\n                }\n            ),\n            .init(\n                id: \"StashedChangesGroup\",\n                label: \"Stashed Changes\",\n                image: .system(name: \"tray.2.fill\"),\n                imageColor: Color(nsColor: .secondaryLabelColor),\n                children: sourceControlManager.stashEntries.map { stashEntry in\n                        .init(\n                            id: \"StashEntry\\(stashEntry.hashValue)\",\n                            label: stashEntry.message,\n                            description: stashEntry.date.formatted(\n                                Date.FormatStyle()\n                                    .year(.defaultDigits)\n                                    .month(.abbreviated)\n                                    .day(.twoDigits)\n                                    .hour(.defaultDigits(amPM: .abbreviated))\n                                    .minute(.twoDigits)\n                            ),\n                            image: .system(name: \"tray\"),\n                            imageColor: .orange,\n                            stashEntry: stashEntry\n                        )\n                }\n            ),\n            .init(\n                id: \"RemotesGroup\",\n                label: \"Remotes\",\n                image: .system(name: \"network\"),\n                imageColor: Color(nsColor: .secondaryLabelColor),\n                children: sourceControlManager.remotes.map { remote in\n                        .init(\n                            id: \"Remote\\(remote.hashValue)\",\n                            label: remote.name,\n                            image: .symbol(name: \"vault\"),\n                            imageColor: .teal,\n                            children: remote.branches.map { branch in\n                                .init(\n                                    id: \"Remote\\(remote.name)-Branch\\(branch.name)\",\n                                    label: branch.name,\n                                    image: .symbol(name: \"branch\"),\n                                    imageColor: .blue,\n                                    branch: branch\n                                )\n                            },\n                            remote: remote\n                        )\n                }\n            )\n        ]\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryView.swift",
    "content": "//\n//  SourceControlNavigatorRepositoryView.swift\n//  CodeEdit\n//\n//  Created by Nanashi Li on 2022/05/20.\n//\n\nimport SwiftUI\nimport CodeEditSymbols\n\nstruct SourceControlNavigatorRepositoryView: View {\n    @Environment(\\.controlActiveState)\n    var controlActiveState\n\n    @EnvironmentObject var sourceControlManager: SourceControlManager\n\n    @State var selection = Set<String>()\n    @State var showNewBranch: Bool = false\n    @State var showRenameBranch: Bool = false\n    @State var fromBranch: GitBranch?\n    @State var expandedIds = [String: Bool]()\n    @State var applyStashedChangesIsPresented: Bool = false\n    @State var isPresentingConfirmDeleteBranch: Bool = false\n    @State var branchToDelete: GitBranch?\n    @State var isPresentingConfirmDeleteStashEntry: Bool = false\n    @State var stashEntryToApply: GitStashEntry?\n    @State var stashEntryToDelete: GitStashEntry?\n    @State var isPresentingConfirmDeleteRemote: Bool = false\n    @State var remoteToDelete: GitRemote?\n    @State var keepStashAfterApplying: Bool = true\n\n    func findItem(by id: String, in items: [RepoOutlineGroupItem]) -> RepoOutlineGroupItem? {\n        for item in items {\n            if item.id == id {\n                return item\n            } else if let children = item.children, let found = findItem(by: id, in: children) {\n                return found\n            }\n        }\n        return nil\n    }\n\n    var body: some View {\n        List(selection: $selection) {\n            ForEach(outlineGroupData, id: \\.id) { item in\n                CEOutlineGroup(\n                    item,\n                    id: \\.id,\n                    defaultExpanded: true,\n                    expandedIds: $expandedIds,\n                    children: \\.children,\n                    content: { item in\n                        SourceControlNavigatorRepositoryItem(item: item)\n                    }\n                )\n                .listRowSeparator(.hidden)\n            }\n        }\n        .environment(\\.defaultMinListRowHeight, 22)\n        .contextMenu(\n            forSelectionType: RepoOutlineGroupItem.ID.self,\n            menu: { items in\n                if !items.isEmpty,\n                   items.count == 1,\n                   let item = findItem(by: items.first ?? \"\", in: outlineGroupData),\n                   let branch = item.branch ?? sourceControlManager.currentBranch {\n                    contextMenu(for: item, branch: branch)\n                }\n            }\n        )\n        .sheet(isPresented: $showNewBranch) {\n            SourceControlNewBranchView(\n                fromBranch: $fromBranch\n            )\n        }\n        .sheet(isPresented: $showRenameBranch) {\n            SourceControlRenameBranchView(\n                fromBranch: $fromBranch\n            )\n        }\n        .alert(\n            sourceControlManager.changedFiles.isEmpty\n            ? \"Do you want to apply stashed changes?\"\n            : \"The local repository has uncommitted changes.\",\n            isPresented: $applyStashedChangesIsPresented\n        ) {\n            if sourceControlManager.changedFiles.isEmpty {\n                Button(\"Apply\") {\n                    if let stashEntry = stashEntryToApply {\n                        Task {\n                            try await sourceControlManager.applyStashEntry(stashEntry: stashEntry)\n                            applyStashedChangesIsPresented = false\n                            stashEntryToApply = nil\n                        }\n                    }\n                }\n                Button(\"Apply and Delete\") {\n                    if let stashEntry = stashEntryToApply {\n                        Task {\n                            try await sourceControlManager.applyStashEntry(stashEntry: stashEntry)\n                            try await sourceControlManager.deleteStashEntry(stashEntry: stashEntry)\n                            applyStashedChangesIsPresented = false\n                            stashEntryToApply = nil\n                        }\n                    }\n                }\n                Button(\"Cancel\", role: .cancel) {}\n            } else {\n                Button(\"Okay\", role: .cancel) {}\n            }\n        } message: {\n            sourceControlManager.changedFiles.isEmpty\n            ? Text(\"Applying the stashed changes will restore modifications to files in your local repository.\")\n            : Text(\"Try committing or discarding the changes.\")\n        }\n        .confirmationDialog(\n            \"Do you want to delete the branch “\\(branchToDelete?.name ?? \"\")”?\",\n            isPresented: $isPresentingConfirmDeleteBranch\n        ) {\n            Button(\"Delete\") {\n                if let branch = branchToDelete {\n                    Task {\n                        do {\n                            try await sourceControlManager.deleteBranch(branch: branch)\n                        } catch {\n                            await sourceControlManager.showAlertForError(\n                                title: \"Failed to delete\",\n                                error: error\n                            )\n                        }\n                        branchToDelete = nil\n                    }\n                }\n            }\n        } message: {\n            Text(\"The branch will be removed from the repository. You can’t undo this action.\")\n        }\n        .confirmationDialog(\n            \"Do you want to delete the stash “\\(stashEntryToDelete?.message ?? \"\")”?\",\n            isPresented: $isPresentingConfirmDeleteStashEntry\n        ) {\n            Button(\"Delete\") {\n                if let stashEntry = stashEntryToDelete {\n                    Task {\n                        do {\n                            try await sourceControlManager.deleteStashEntry(stashEntry: stashEntry)\n                        } catch {\n                            await sourceControlManager.showAlertForError(\n                                title: \"Failed to delete\",\n                                error: error\n                            )\n                        }\n                        stashEntryToDelete = nil\n                    }\n                }\n            }\n        } message: {\n            Text(\"The stash will be removed from the repository. You can’t undo this action.\")\n        }\n        .confirmationDialog(\n            \"Do you want to delete the remote “\\(remoteToDelete?.name ?? \"\")”?\",\n            isPresented: $isPresentingConfirmDeleteRemote\n        ) {\n            Button(\"Delete\") {\n                if let remote = remoteToDelete {\n                    Task {\n                        do {\n                            try await sourceControlManager.deleteRemote(remote: remote)\n                        } catch {\n                            await sourceControlManager.showAlertForError(\n                                title: \"Failed to delete\",\n                                error: error\n                            )\n                        }\n                        remoteToDelete = nil\n                    }\n                }\n            }\n        } message: {\n            Text(\"The remote will be removed from the repository. You can’t undo this action.\")\n        }\n        .task {\n            await sourceControlManager.refreshBranches()\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/ChangedFile/GitChangedFileLabel.swift",
    "content": "//\n//  GitChangedFileLabel.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 8/23/24.\n//\n\nimport SwiftUI\n\nstruct GitChangedFileLabel: View {\n    @EnvironmentObject private var workspace: WorkspaceDocument\n    @EnvironmentObject private var sourceControlManager: SourceControlManager\n\n    let file: GitChangedFile\n\n    var body: some View {\n        Label {\n            Text(file.fileURL.lastPathComponent.trimmingCharacters(in: .whitespacesAndNewlines))\n                .lineLimit(1)\n                .truncationMode(.middle)\n        } icon: {\n            if let ceFile = workspace.workspaceFileManager?.getFile(file.ceFileKey, createIfNotFound: true) {\n                Image(nsImage: ceFile.nsIcon)\n                    .renderingMode(.template)\n            } else {\n                Image(systemName: FileIcon.fileIcon(fileType: nil))\n                    .renderingMode(.template)\n            }\n        }\n    }\n}\n\n#Preview {\n    Group {\n        GitChangedFileLabel(file: GitChangedFile(\n            status: .modified,\n            stagedStatus: .none,\n            fileURL: URL(filePath: \"/Users/CodeEdit/app.jsx\"),\n            originalFilename: nil\n        ))\n        .environmentObject(SourceControlManager(workspaceURL: URL(filePath: \"/Users/CodeEdit\"), editorManager: .init()))\n        .environmentObject(WorkspaceDocument())\n\n        GitChangedFileLabel(file: GitChangedFile(\n            status: .none,\n            stagedStatus: .renamed,\n            fileURL: URL(filePath: \"/Users/CodeEdit/app.jsx\"),\n            originalFilename: \"app2.jsx\"\n        ))\n        .environmentObject(SourceControlManager(workspaceURL: URL(filePath: \"/Users/CodeEdit\"), editorManager: .init()))\n        .environmentObject(WorkspaceDocument())\n    }.padding()\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/ChangedFile/GitChangedFileListView.swift",
    "content": "//\n//  GitChangedFileListView.swift\n//  CodeEdit\n//\n//  Created by Nanashi Li on 2022/05/20.\n//\n\nimport SwiftUI\n\n/// A view to display a changed file's information in a list view. Optionally displays the staged status.\nstruct GitChangedFileListView: View {\n    @AppSettings(\\.general.fileIconStyle)\n    private var fileIconStyle\n    @EnvironmentObject private var workspace: WorkspaceDocument\n    @EnvironmentObject private var sourceControlManager: SourceControlManager\n    @Binding private var changedFile: GitChangedFile\n\n    @State private var staged: Bool\n    private let showStaged: Bool\n\n    init(changedFile: Binding<GitChangedFile>, showStaged: Bool = true) {\n        self._changedFile = changedFile\n        self.showStaged = showStaged\n        self._staged = State(initialValue: changedFile.wrappedValue.isStaged)\n    }\n\n    var body: some View {\n        HStack(spacing: 6) {\n            if showStaged {\n                Toggle(\"\", isOn: $staged)\n                    .labelsHidden()\n                    .onChange(of: staged) { _, newStaged in\n                        Task {\n                            if changedFile.isStaged != newStaged {\n                                if newStaged {\n                                    try await sourceControlManager.add([changedFile.fileURL])\n                                } else {\n                                    try await sourceControlManager.reset([changedFile.fileURL])\n                                }\n                            }\n                        }\n                    }\n            }\n\n            GitChangedFileLabel(file: changedFile)\n            Spacer()\n            Text(changedFile.anyStatus().description)\n                .font(.system(size: 11, weight: .bold))\n                .foregroundColor(.secondary)\n                .frame(minWidth: 10, alignment: .center)\n        }\n        .listItemTint(listItemTint)\n        .help(changedFile.fileURL.relativePath)\n        .onChange(of: changedFile.isStaged) { _, newStaged in\n            staged = newStaged\n        }\n    }\n\n    private var listItemTint: Color {\n        if let ceFile = workspace.workspaceFileManager?.getFile(changedFile.ceFileKey, createIfNotFound: true) {\n            iconForegroundColor(ceFile)\n        } else {\n            iconForegroundColor(nil)\n        }\n    }\n\n    private func iconForegroundColor(_ file: CEWorkspaceFile?) -> Color {\n        switch fileIconStyle {\n        case .color:\n            if let file {\n                return file.iconColor\n            } else {\n                return FileIcon.iconColor(fileType: nil)\n            }\n        case .monochrome:\n            return Color(\"CoolGray\")\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorToolbarBottom.swift",
    "content": "//\n//  SourceControlNavigatorToolbarBottom.swift\n//  CodeEdit\n//\n//  Created by Nanashi Li on 2022/05/20.\n//\n\nimport SwiftUI\n\nstruct SourceControlNavigatorToolbarBottom: View {\n    @EnvironmentObject private var workspace: WorkspaceDocument\n    @EnvironmentObject var sourceControlManager: SourceControlManager\n\n    @State private var text = \"\"\n\n    var body: some View {\n        HStack(spacing: 5) {\n            sourceControlMenu\n            PaneTextField(\n                \"Filter\",\n                text: $text,\n                leadingAccessories: {\n                    Image(\n                        systemName: text.isEmpty\n                        ? \"line.3.horizontal.decrease.circle\"\n                        : \"line.3.horizontal.decrease.circle.fill\"\n                    )\n                    .foregroundStyle(\n                        text.isEmpty\n                        ? Color(nsColor: .secondaryLabelColor)\n                        : Color(nsColor: .controlAccentColor)\n                    )\n                    .padding(.leading, 4)\n                    .help(\"Filter Changes Navigator\")\n                },\n                clearable: true\n            )\n        }\n        .frame(height: 28, alignment: .center)\n        .frame(maxWidth: .infinity)\n        .padding(.horizontal, 5)\n        .overlay(alignment: .top) {\n            Divider()\n                .opacity(0)\n        }\n    }\n\n    private var sourceControlMenu: some View {\n        Menu {\n            Button(\"Discard All Changes...\") {\n                if sourceControlManager.changedFiles.isEmpty {\n                    sourceControlManager.noChangesToDiscardAlertIsPresented = true\n                } else {\n                    sourceControlManager.discardAllAlertIsPresented = true\n                }\n            }\n            Button(\"Stash Changes...\") {\n                if sourceControlManager.changedFiles.isEmpty {\n                    sourceControlManager.noChangesToStashAlertIsPresented = true\n                } else {\n                    sourceControlManager.stashSheetIsPresented = true\n                }\n            }\n        } label: {}\n        .background {\n            Image(systemName: \"ellipsis.circle\")\n        }\n        .menuStyle(.borderlessButton)\n        .menuIndicator(.hidden)\n        .frame(maxWidth: 18, alignment: .center)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorView.swift",
    "content": "//\n//  SourceControlNavigatorView.swift\n//  CodeEdit\n//\n//  Created by Nanashi Li on 2022/05/20.\n//\n\nimport SwiftUI\n\nstruct SourceControlNavigatorView: View {\n    @EnvironmentObject private var workspace: WorkspaceDocument\n\n    @AppSettings(\\.sourceControl.general.fetchRefreshServerStatus)\n    var fetchRefreshServerStatus\n\n    var body: some View {\n        if let sourceControlManager = workspace.workspaceFileManager?.sourceControlManager {\n            VStack(spacing: 0) {\n                SourceControlNavigatorTabs()\n                    .environmentObject(sourceControlManager)\n                    .task {\n                        do {\n                            while true {\n                                if fetchRefreshServerStatus {\n                                    try await sourceControlManager.fetch()\n                                }\n                                try await Task.sleep(for: .seconds(10))\n                            }\n                        } catch {\n                            // TODO: if source fetching fails, display message\n                        }\n                    }\n            }\n            .safeAreaInset(edge: .bottom, spacing: 0) {\n                SourceControlNavigatorToolbarBottom()\n                    .environmentObject(sourceControlManager)\n            }\n        }\n    }\n}\n\nstruct SourceControlNavigatorTabs: View {\n    @EnvironmentObject var sourceControlManager: SourceControlManager\n    @State private var selectedSection: Int = 0\n\n    var body: some View {\n        if sourceControlManager.isGitRepository {\n            SegmentedControl(\n                $selectedSection,\n                options: [\"Changes\", \"History\", \"Repository\"],\n                prominent: true\n            )\n            .frame(maxWidth: .infinity)\n            .frame(height: 27)\n            .padding(.horizontal, 8)\n            Divider()\n            if selectedSection == 0 {\n                SourceControlNavigatorChangesView()\n            }\n            if selectedSection == 1 {\n                SourceControlNavigatorHistoryView()\n            }\n            if selectedSection == 2 {\n                SourceControlNavigatorRepositoryView()\n            }\n        } else {\n            CEContentUnavailableView(\n                \"No Repository\",\n                 description: \"This project is not a git repository.\",\n                 systemImage: \"externaldrive.fill\",\n                 actions: {\n                    Button(\"Initialize\") {\n                        Task {\n                            try await sourceControlManager.initiate()\n                        }\n                    }\n                }\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/ViewModels/NavigatorAreaViewModel.swift",
    "content": "//\n//  NavigatorAreaViewModel.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 7/23/23.\n//\n\nimport Foundation\n\nclass NavigatorAreaViewModel: ObservableObject {\n    @Published var selectedTab: NavigatorTab? = .project\n    /// The tab bar items in the Navigator\n    @Published var tabItems: [NavigatorTab] = []\n\n    func setNavigatorTab(tab newTab: NavigatorTab) {\n        selectedTab = newTab\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift",
    "content": "//\n//  NavigatorAreaView.swift\n//  CodeEdit\n//\n//  Created by Lukas Pistrol on 17.03.22.\n//\n\nimport SwiftUI\n\nstruct NavigatorAreaView: View {\n    @ObservedObject private var workspace: WorkspaceDocument\n    @ObservedObject private var extensionManager = ExtensionManager.shared\n    @ObservedObject public var viewModel: NavigatorAreaViewModel\n\n    @AppSettings(\\.general.navigatorTabBarPosition)\n    var sidebarPosition: SettingsData.SidebarTabBarPosition\n\n    init(workspace: WorkspaceDocument, viewModel: NavigatorAreaViewModel) {\n        self.workspace = workspace\n        self.viewModel = viewModel\n\n        viewModel.tabItems = [.project, .sourceControl, .search] +\n            extensionManager\n                .extensions\n                .map { ext in\n                    ext.availableFeatures.compactMap {\n                        if case .sidebarItem(let data) = $0, data.kind == .navigator {\n                            return NavigatorTab.uiExtension(endpoint: ext.endpoint, data: data)\n                        }\n                        return nil\n                    }\n                }\n                .joined()\n    }\n\n    var body: some View {\n        WorkspacePanelView(\n            viewModel: viewModel,\n            selectedTab: $viewModel.selectedTab,\n            tabItems: $viewModel.tabItems,\n            sidebarPosition: sidebarPosition\n        )\n        .environmentObject(workspace)\n        .accessibilityElement(children: .contain)\n        .accessibilityLabel(\"navigator\")\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Notifications/Models/CENotification.swift",
    "content": "//\n//  CENotification.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 2/10/24.\n//\n\nimport Foundation\nimport SwiftUI\n\nstruct CENotification: Identifiable, Equatable {\n    let id: UUID\n    let icon: IconType\n    let title: String\n    let description: String\n    let actionButtonTitle: String\n    let action: () -> Void\n    let isSticky: Bool\n    var isRead: Bool\n    let timestamp: Date\n    var isBeingDismissed: Bool = false\n\n    enum IconType {\n        case symbol(name: String, color: Color?)\n        case image(Image)\n        case text(String, backgroundColor: Color?, textColor: Color?)\n    }\n\n    init(\n        id: UUID = UUID(),\n        iconSymbol: String,\n        iconColor: Color? = nil,\n        title: String,\n        description: String,\n        actionButtonTitle: String,\n        action: @escaping () -> Void,\n        isSticky: Bool = false,\n        isRead: Bool = false\n    ) {\n        self.init(\n            id: id,\n            icon: .symbol(name: iconSymbol, color: iconColor),\n            title: title,\n            description: description,\n            actionButtonTitle: actionButtonTitle,\n            action: action,\n            isSticky: isSticky,\n            isRead: isRead\n        )\n    }\n\n    init(\n        id: UUID = UUID(),\n        iconText: String,\n        iconTextColor: Color? = nil,\n        iconColor: Color? = nil,\n        title: String,\n        description: String,\n        actionButtonTitle: String,\n        action: @escaping () -> Void,\n        isSticky: Bool = false,\n        isRead: Bool = false\n    ) {\n        self.init(\n            id: id,\n            icon: .text(iconText, backgroundColor: iconColor, textColor: iconTextColor),\n            title: title,\n            description: description,\n            actionButtonTitle: actionButtonTitle,\n            action: action,\n            isSticky: isSticky,\n            isRead: isRead\n        )\n    }\n\n    init(\n        id: UUID = UUID(),\n        iconImage: Image,\n        title: String,\n        description: String,\n        actionButtonTitle: String,\n        action: @escaping () -> Void,\n        isSticky: Bool = false,\n        isRead: Bool = false\n    ) {\n        self.init(\n            id: id,\n            icon: .image(iconImage),\n            title: title,\n            description: description,\n            actionButtonTitle: actionButtonTitle,\n            action: action,\n            isSticky: isSticky,\n            isRead: isRead\n        )\n    }\n\n    private init(\n        id: UUID,\n        icon: IconType,\n        title: String,\n        description: String,\n        actionButtonTitle: String,\n        action: @escaping () -> Void,\n        isSticky: Bool,\n        isRead: Bool\n    ) {\n        self.id = id\n        self.icon = icon\n        self.title = title\n        self.description = description\n        self.actionButtonTitle = actionButtonTitle\n        self.action = action\n        self.isSticky = isSticky\n        self.isRead = isRead\n        self.timestamp = Date()\n    }\n\n    static func == (lhs: CENotification, rhs: CENotification) -> Bool {\n        lhs.id == rhs.id\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Notifications/NotificationManager+Delegate.swift",
    "content": "//\n//  NotificationManager+Delegate.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 2/14/24.\n//\n\nimport AppKit\nimport UserNotifications\n\nextension NotificationManager: UNUserNotificationCenterDelegate {\n    func userNotificationCenter(\n        _ center: UNUserNotificationCenter,\n        didReceive response: UNNotificationResponse,\n        withCompletionHandler completionHandler: @escaping () -> Void\n    ) {\n        if let notification = notifications.first(where: {\n            $0.id.uuidString == response.notification.request.identifier\n        }) {\n            // Focus CodeEdit and run action if action button was clicked\n            if response.actionIdentifier == \"ACTION_BUTTON\" ||\n               response.actionIdentifier == UNNotificationDefaultActionIdentifier {\n                NSApp.activate(ignoringOtherApps: true)\n                notification.action()\n            }\n\n            // Remove the notification for both action and dismiss\n            if response.actionIdentifier == \"ACTION_BUTTON\" ||\n               response.actionIdentifier == UNNotificationDefaultActionIdentifier ||\n               response.actionIdentifier == UNNotificationDismissActionIdentifier {\n                dismissNotification(notification)\n            }\n        }\n\n        completionHandler()\n    }\n\n    func userNotificationCenter(\n        _ center: UNUserNotificationCenter,\n        willPresent notification: UNNotification,\n        withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void\n    ) {\n        completionHandler([.banner, .sound])\n    }\n\n    func setupNotificationDelegate() {\n        UNUserNotificationCenter.current().delegate = self\n\n        // Create action button\n        let action = UNNotificationAction(\n            identifier: \"ACTION_BUTTON\",\n            title: \"Action\", // This will be replaced with actual button title\n            options: .foreground\n        )\n\n        // Create category with action button\n        let actionCategory = UNNotificationCategory(\n            identifier: \"ACTIONABLE\",\n            actions: [action],\n            intentIdentifiers: [],\n            options: .customDismissAction\n        )\n\n        UNUserNotificationCenter.current().setNotificationCategories([actionCategory])\n        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { _, _ in }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Notifications/NotificationManager+System.swift",
    "content": "//\n//  NotificationManager+System.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 2/14/24.\n//\n\nimport Foundation\nimport UserNotifications\n\nextension NotificationManager {\n    /// Shows a system notification when app is in background\n    func showSystemNotification(_ notification: CENotification) {\n        let content = UNMutableNotificationContent()\n        content.title = notification.title\n        content.body = notification.description\n\n        if !notification.actionButtonTitle.isEmpty {\n            content.categoryIdentifier = \"ACTIONABLE\"\n        }\n\n        let request = UNNotificationRequest(\n            identifier: notification.id.uuidString,\n            content: content,\n            trigger: nil\n        )\n\n        UNUserNotificationCenter.current().add(request)\n    }\n\n    /// Removes a system notification\n    func removeSystemNotification(_ notification: CENotification) {\n        UNUserNotificationCenter.current().removeDeliveredNotifications(\n            withIdentifiers: [notification.id.uuidString]\n        )\n    }\n\n    /// Handles response from system notification\n    func handleSystemNotificationResponse(id: String) {\n        if let uuid = UUID(uuidString: id),\n           let notification = notifications.first(where: { $0.id == uuid }) {\n            notification.action()\n            dismissNotification(notification)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Notifications/NotificationManager.swift",
    "content": "//\n//  NotificationManager.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 2/10/24.\n//\n\nimport SwiftUI\nimport Combine\nimport UserNotifications\n\n/// Manages the application's notification system, handling both in-app notifications and system notifications.\n/// This class is responsible for:\n/// - Managing notification persistence\n/// - Tracking notification read status\n/// - Broadcasting notifications to workspaces\nfinal class NotificationManager: NSObject, ObservableObject {\n    /// Shared instance for accessing the notification manager\n    static let shared = NotificationManager()\n\n    /// Collection of all notifications, both read and unread\n    @Published private(set) var notifications: [CENotification] = []\n\n    private var isAppActive: Bool = true\n\n    /// Number of unread notifications\n    var unreadCount: Int {\n        notifications.filter { !$0.isRead }.count\n    }\n\n    /// Posts a new notification\n    /// - Parameters:\n    ///   - iconSymbol: SF Symbol or CodeEditSymbol name for the notification icon\n    ///   - iconColor: Color for the icon\n    ///   - title: Main notification title\n    ///   - description: Detailed notification message\n    ///   - actionButtonTitle: Title for the action button\n    ///   - action: Closure to execute when action button is clicked\n    ///   - isSticky: Whether the notification should persist until manually dismissed\n    func post(\n        iconSymbol: String,\n        iconColor: Color? = Color(.systemBlue),\n        title: String,\n        description: String,\n        actionButtonTitle: String,\n        action: @escaping () -> Void,\n        isSticky: Bool = false\n    ) {\n        let notification = CENotification(\n            iconSymbol: iconSymbol,\n            iconColor: iconColor,\n            title: title,\n            description: description,\n            actionButtonTitle: actionButtonTitle,\n            action: action,\n            isSticky: isSticky,\n            isRead: false\n        )\n\n        postNotification(notification)\n    }\n\n    /// Posts a new notification\n    /// - Parameters:\n    ///   - iconImage: Image for the notification icon\n    ///   - title: Main notification title\n    ///   - description: Detailed notification message\n    ///   - actionButtonTitle: Title for the action button\n    ///   - action: Closure to execute when action button is clicked\n    ///   - isSticky: Whether the notification should persist until manually dismissed\n    func post(\n        iconImage: Image,\n        title: String,\n        description: String,\n        actionButtonTitle: String,\n        action: @escaping () -> Void,\n        isSticky: Bool = false\n    ) {\n        let notification = CENotification(\n            iconImage: iconImage,\n            title: title,\n            description: description,\n            actionButtonTitle: actionButtonTitle,\n            action: action,\n            isSticky: isSticky\n        )\n\n        postNotification(notification)\n    }\n\n    /// Posts a new notification\n    /// - Parameters:\n    ///   - iconText: Text or emoji for the notification icon\n    ///   - iconTextColor: Color of the text/emoji (defaults to primary label color)\n    ///   - iconColor: Background color for the icon\n    ///   - title: Main notification title\n    ///   - description: Detailed notification message\n    ///   - actionButtonTitle: Title for the action button\n    ///   - action: Closure to execute when action button is clicked\n    ///   - isSticky: Whether the notification should persist until manually dismissed\n    func post(\n        iconText: String,\n        iconTextColor: Color? = nil,\n        iconColor: Color? = Color(.systemBlue),\n        title: String,\n        description: String,\n        actionButtonTitle: String,\n        action: @escaping () -> Void,\n        isSticky: Bool = false\n    ) {\n        let notification = CENotification(\n            iconText: iconText,\n            iconTextColor: iconTextColor,\n            iconColor: iconColor,\n            title: title,\n            description: description,\n            actionButtonTitle: actionButtonTitle,\n            action: action,\n            isSticky: isSticky\n        )\n\n        postNotification(notification)\n    }\n\n    /// Dismisses a specific notification\n    func dismissNotification(_ notification: CENotification) {\n        notifications.removeAll(where: { $0.id == notification.id })\n        markAsRead(notification)\n\n        // Remove system notification if it exists\n        removeSystemNotification(notification)\n\n        NotificationCenter.default.post(\n            name: .init(\"NotificationDismissed\"),\n            object: notification\n        )\n    }\n\n    /// Marks a notification as read\n    /// - Parameter notification: The notification to mark as read\n    func markAsRead(_ notification: CENotification) {\n        if let index = notifications.firstIndex(where: { $0.id == notification.id }) {\n            notifications[index].isRead = true\n        }\n    }\n\n    override init() {\n        super.init()\n        setupNotificationDelegate()\n\n        // Observe app active state\n        NotificationCenter.default.addObserver(\n            self,\n            selector: #selector(handleAppDidBecomeActive),\n            name: NSApplication.didBecomeActiveNotification,\n            object: nil\n        )\n\n        NotificationCenter.default.addObserver(\n            self,\n            selector: #selector(handleAppDidResignActive),\n            name: NSApplication.didResignActiveNotification,\n            object: nil\n        )\n    }\n\n    @objc\n    private func handleAppDidBecomeActive() {\n        isAppActive = true\n        // Remove any system notifications when app becomes active\n        UNUserNotificationCenter.current().removeAllDeliveredNotifications()\n    }\n\n    @objc\n    private func handleAppDidResignActive() {\n        isAppActive = false\n    }\n\n    /// Posts a notification to workspaces and system\n    private func postNotification(_ notification: CENotification) {\n        DispatchQueue.main.async { [weak self] in\n            self?.notifications.append(notification)\n\n            // Always notify workspaces of new notification\n            NotificationCenter.default.post(\n                name: .init(\"NewNotificationAdded\"),\n                object: notification\n            )\n\n            // Additionally show system notification when app is in background\n            if self?.isAppActive != true {\n                self?.showSystemNotification(notification)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Notifications/ViewModels/NotificationPanelViewModel.swift",
    "content": "//\n//  NotificationPanelViewModel.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 2/14/24.\n//\n\nimport SwiftUI\n\nfinal class NotificationPanelViewModel: ObservableObject {\n    /// Currently displayed notifications in the panel\n    @Published private(set) var activeNotifications: [CENotification] = []\n\n    /// Whether notifications panel was manually shown via toolbar\n    @Published private(set) var isPresented: Bool = false\n\n    /// Set of hidden notification IDs\n    @Published private(set) var hiddenNotificationIds: Set<UUID> = []\n\n    /// Timers for notifications\n    private var timers: [UUID: Timer] = [:]\n\n    /// Display duration for notifications\n    private let displayDuration: TimeInterval = 5.0\n\n    /// Whether notifications are paused\n    private var isPaused: Bool = false\n\n    private var notificationManager = NotificationManager.shared\n\n    @Published var scrolledToTop: Bool = true\n\n    /// A filtered list of active notifications.\n    var visibleNotifications: [CENotification] {\n        activeNotifications.filter { !hiddenNotificationIds.contains($0.id) }\n    }\n\n    weak var workspace: WorkspaceDocument?\n\n    /// Whether a notification should be visible in the panel\n    func isNotificationVisible(_ notification: CENotification) -> Bool {\n        if notification.isBeingDismissed {\n            return true // Always show notifications being dismissed\n        }\n        if notification.isSticky {\n            return true // Always show sticky notifications\n        }\n        if isPresented {\n            return true // Show all notifications when manually shown\n        }\n        return !hiddenNotificationIds.contains(notification.id)\n    }\n\n    /// Handles focus changes for the notification panel\n    func handleFocusChange(isFocused: Bool) {\n        if !isFocused {\n            // Only hide if manually shown and focus is completely lost\n            if isPresented {\n                toggleNotificationsVisibility()\n            }\n        }\n    }\n\n    /// Toggles visibility of notifications in the panel\n    func toggleNotificationsVisibility() {\n        if isPresented {\n            if !scrolledToTop {\n                // Just set isPresented to false to trigger the offset animation\n                withAnimation(.easeInOut(duration: 0.3)) {\n                    isPresented = false\n                }\n\n                // After the slide-out animation, hide notifications\n                DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {\n                    // Hide non-sticky notifications\n                    self.activeNotifications\n                        .filter { !$0.isSticky }\n                        .forEach { self.hiddenNotificationIds.insert($0.id) }\n                    self.objectWillChange.send()\n\n                    // After notifications are hidden, reset scroll position\n                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {\n                        self.scrolledToTop = true\n                    }\n                }\n            } else {\n                // At top, just hide normally\n                hideNotifications()\n            }\n        } else {\n            withAnimation(.easeInOut(duration: 0.3)) {\n                isPresented = true\n                hiddenNotificationIds.removeAll()\n                objectWillChange.send()\n            }\n        }\n    }\n\n    private func hideNotifications() {\n        withAnimation(.easeInOut(duration: 0.3)) {\n            self.isPresented = false\n            self.activeNotifications\n                .filter { !$0.isSticky }\n                .forEach { self.hiddenNotificationIds.insert($0.id) }\n            self.objectWillChange.send()\n        }\n    }\n\n    /// Starts the timer to automatically hide a notification\n    func startHideTimer(for notification: CENotification) {\n        guard !notification.isSticky && !isPresented else { return }\n\n        timers[notification.id]?.invalidate()\n        timers[notification.id] = nil\n\n        guard !isPaused else { return }\n\n        timers[notification.id] = Timer.scheduledTimer(\n            withTimeInterval: displayDuration,\n            repeats: false\n        ) { [weak self] _ in\n            guard let self = self else { return }\n            self.timers[notification.id] = nil\n\n            // Ensure we're on the main thread and animate the change\n            DispatchQueue.main.async {\n                NSAnimationContext.runAnimationGroup { context in\n                    context.duration = 0.3\n                    context.allowsImplicitAnimation = true\n\n                    withAnimation(.easeInOut(duration: 0.3)) {\n                        var newHiddenIds = self.hiddenNotificationIds\n                        newHiddenIds.insert(notification.id)\n                        self.hiddenNotificationIds = newHiddenIds\n                    }\n                }\n            }\n        }\n    }\n\n    /// Pauses all auto-hide timers\n    func pauseTimer() {\n        isPaused = true\n        timers.values.forEach { $0.invalidate() }\n    }\n\n    /// Resumes all auto-hide timers\n    func resumeTimer() {\n        isPaused = false\n        // Only restart timers for notifications that are currently visible\n        activeNotifications\n            .filter { !$0.isSticky && isNotificationVisible($0) }\n            .forEach { startHideTimer(for: $0) }\n    }\n\n    /// Inserts a notification in the correct position (sticky notifications on top)\n    private func insertNotification(_ notification: CENotification) {\n        if notification.isSticky {\n            // Find the first sticky notification (to insert before it)\n            if let firstStickyIndex = activeNotifications.firstIndex(where: { $0.isSticky }) {\n                // Insert at the very start of sticky group\n                activeNotifications.insert(notification, at: firstStickyIndex)\n            } else {\n                // No sticky notifications yet, insert at the start\n                activeNotifications.insert(notification, at: 0)\n            }\n        } else {\n            // Find the first non-sticky notification\n            if let firstNonStickyIndex = activeNotifications.firstIndex(where: { !$0.isSticky }) {\n                // Insert at the start of non-sticky group\n                activeNotifications.insert(notification, at: firstNonStickyIndex)\n            } else {\n                // No non-sticky notifications yet, append at the end\n                activeNotifications.append(notification)\n            }\n        }\n    }\n\n    /// Handles a new notification being added\n    func handleNewNotification(_ notification: CENotification) {\n        let operation = {\n            self.insertNotification(notification)\n            self.hiddenNotificationIds.remove(notification.id)\n            if !self.isPresented && !notification.isSticky {\n                self.startHideTimer(for: notification)\n            }\n        }\n\n        if #available(macOS 26, *) {\n            withAnimation(.easeInOut(duration: 0.3), operation) {\n                self.updateToolbarItem()\n            }\n        } else {\n            withAnimation(.easeInOut(duration: 0.3), operation)\n        }\n    }\n\n    /// Dismisses a specific notification\n    func dismissNotification(_ notification: CENotification, disableAnimation: Bool = false) {\n        // Clean up timers\n        timers[notification.id]?.invalidate()\n        timers[notification.id] = nil\n        hiddenNotificationIds.remove(notification.id)\n\n        // Mark as being dismissed for animation\n        if let index = activeNotifications.firstIndex(where: { $0.id == notification.id }) {\n            if disableAnimation {\n                self.activeNotifications.removeAll(where: { $0.id == notification.id })\n                NotificationManager.shared.markAsRead(notification)\n                NotificationManager.shared.dismissNotification(notification)\n                return\n            }\n\n            var dismissingNotification = activeNotifications[index]\n            dismissingNotification.isBeingDismissed = true\n            activeNotifications[index] = dismissingNotification\n\n            // Wait for fade animation before removing\n            DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {\n                withAnimation(.easeOut(duration: 0.2)) {\n                    self.activeNotifications.removeAll(where: { $0.id == notification.id })\n                    if self.activeNotifications.isEmpty && self.isPresented {\n                        self.isPresented = false\n                    }\n                }\n\n                NotificationManager.shared.markAsRead(notification)\n                NotificationManager.shared.dismissNotification(notification)\n            }\n        }\n    }\n\n    func updateToolbarItem() {\n        if #available(macOS 15.0, *) {\n            self.workspace?.windowControllers.forEach { controller in\n                guard let toolbar = controller.window?.toolbar else {\n                    return\n                }\n                let shouldShow = !self.visibleNotifications.isEmpty || NotificationManager.shared.unreadCount > 0\n                if shouldShow && toolbar.items.filter({ $0.itemIdentifier == .notificationItem }).first == nil {\n                    guard let activityItemIdx = toolbar.items\n                        .firstIndex(where: { $0.itemIdentifier == .activityViewer }) else {\n                        return\n                    }\n                    toolbar.insertItem(withItemIdentifier: .space, at: activityItemIdx + 1)\n                    toolbar.insertItem(withItemIdentifier: .notificationItem, at: activityItemIdx + 2)\n                }\n\n                if !shouldShow, let index = toolbar.items\n                    .firstIndex(where: { $0.itemIdentifier == .notificationItem }) {\n                    toolbar.removeItem(at: index)\n                    toolbar.removeItem(at: index)\n                }\n            }\n        }\n    }\n\n    init() {\n        // Observe new notifications\n        NotificationCenter.default.addObserver(\n            self,\n            selector: #selector(handleNewNotificationAdded(_:)),\n            name: .init(\"NewNotificationAdded\"),\n            object: nil\n        )\n\n        // Observe notification dismissals\n        NotificationCenter.default.addObserver(\n            self,\n            selector: #selector(handleNotificationRemoved(_:)),\n            name: .init(\"NotificationDismissed\"),\n            object: nil\n        )\n\n        // Load initial notifications from NotificationManager\n        notificationManager.notifications.forEach { notification in\n            handleNewNotification(notification)\n        }\n    }\n\n    deinit {\n        NotificationCenter.default.removeObserver(self)\n    }\n\n    @objc\n    private func handleNewNotificationAdded(_ notification: Notification) {\n        guard let ceNotification = notification.object as? CENotification else { return }\n        handleNewNotification(ceNotification)\n    }\n\n    @objc\n    private func handleNotificationRemoved(_ notification: Notification) {\n        guard let ceNotification = notification.object as? CENotification else { return }\n\n        let operation: () -> Void = {\n            self.activeNotifications.removeAll(where: { $0.id == ceNotification.id })\n\n            // If this was the last notification and they were manually shown, hide the panel\n            if self.activeNotifications.isEmpty && self.isPresented {\n                self.isPresented = false\n            }\n        }\n\n        // Just remove from active notifications without triggering global state changes\n        if #available(macOS 26, *) {\n            withAnimation(.easeOut(duration: 0.2), operation) {\n                self.updateToolbarItem()\n            }\n        } else {\n            withAnimation(.easeOut(duration: 0.2), operation)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Notifications/Views/NotificationBannerView.swift",
    "content": "//\n//  NotificationBannerView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 2/10/24.\n//\n\nimport SwiftUI\n\nstruct NotificationBannerView: View {\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @EnvironmentObject private var workspace: WorkspaceDocument\n    @ObservedObject private var notificationManager = NotificationManager.shared\n\n    let notification: CENotification\n    let onDismiss: () -> Void\n    let onAction: () -> Void\n\n    @State private var isHovering = false\n\n    let cornerRadius: CGFloat = 10\n\n    private var backgroundContainer: some View {\n        RoundedRectangle(cornerRadius: cornerRadius)\n            .fill(.regularMaterial)\n    }\n\n    private var borderOverlay: some View {\n        RoundedRectangle(cornerRadius: cornerRadius)\n            .stroke(Color(nsColor: .separatorColor), lineWidth: 2)\n    }\n\n    var body: some View {\n        VStack(spacing: 10) {\n            HStack(alignment: .top, spacing: 10) {\n                switch notification.icon {\n                case let .symbol(name, color):\n                    FeatureIcon(\n                        symbol: name,\n                        color: color ?? Color(.systemBlue),\n                        size: 26\n                    )\n                case let .text(text, backgroundColor, textColor):\n                    FeatureIcon(\n                        text: text,\n                        textColor: textColor ?? .primary,\n                        color: backgroundColor ?? Color(.systemBlue),\n                        size: 26\n                    )\n                case let .image(image):\n                    FeatureIcon(\n                        image: image,\n                        size: 26\n                    )\n                }\n                VStack(alignment: .leading, spacing: 1) {\n                    Text(notification.title)\n                        .font(.system(size: 12))\n                        .fontWeight(.semibold)\n                        .padding(.top, -3)\n                    Text(notification.description)\n                        .font(.callout)\n                        .foregroundColor(.secondary)\n                }\n                .frame(maxWidth: .infinity, alignment: .leading)\n                .mask(\n                    LinearGradient(\n                        gradient: Gradient(\n                            colors: [\n                                .black,\n                                .black,\n                                !notification.isSticky && isHovering ? .clear : .black,\n                                !notification.isSticky && isHovering ? .clear : .black\n                            ]\n                        ),\n                        startPoint: .leading,\n                        endPoint: .trailing\n                    )\n                )\n            }\n            if notification.isSticky {\n                HStack(spacing: 8) {\n                    Button(action: onDismiss, label: {\n                        Text(\"Dismiss\")\n                            .frame(maxWidth: .infinity)\n                    })\n                    .buttonStyle(.secondaryBlur)\n                    .controlSize(.small)\n                    Button(action: onAction, label: {\n                        Text(notification.actionButtonTitle)\n                            .frame(maxWidth: .infinity)\n                    })\n                    .buttonStyle(.secondaryBlur)\n                    .controlSize(.small)\n                }\n                .transition(.opacity.combined(with: .move(edge: .top)))\n            }\n        }\n        .padding(10)\n        .background(backgroundContainer)\n        .overlay(borderOverlay)\n        .cornerRadius(cornerRadius)\n        .shadow(\n            color: Color(.black.withAlphaComponent(colorScheme == .dark ? 0.2 : 0.1)),\n            radius: 5,\n            x: 0,\n            y: 2\n        )\n        .overlay(alignment: .bottomTrailing) {\n            if !notification.isSticky && isHovering {\n                Button(action: onAction, label: {\n                    Text(notification.actionButtonTitle)\n                })\n                .buttonStyle(.secondaryBlur)\n                .controlSize(.small)\n                .padding(10)\n                .transition(.opacity)\n            }\n        }\n        .overlay(alignment: .topLeading) {\n            if !notification.isSticky && isHovering {\n                Button(action: onDismiss) {\n                    Image(systemName: \"xmark\")\n                }\n                .buttonStyle(.overlay)\n                .padding(.top, -5)\n                .padding(.leading, -5)\n                .transition(.opacity)\n            }\n        }\n        .frame(width: 300)\n        .transition(.asymmetric(\n            insertion: .move(edge: .trailing),\n            removal: .modifier(\n                active: DismissTransition(\n                    useOpactityTransition: notification.isBeingDismissed,\n                    isIdentity: false\n                ),\n                identity: DismissTransition(\n                    useOpactityTransition: notification.isBeingDismissed,\n                    isIdentity: true\n                )\n            )\n        ))\n        .onHover { hovering in\n            withAnimation(.easeOut(duration: 0.2)) {\n                isHovering = hovering\n            }\n\n            if hovering {\n                workspace.notificationPanel.pauseTimer()\n            } else {\n                workspace.notificationPanel.resumeTimer()\n            }\n        }\n    }\n}\n\nstruct DismissTransition: ViewModifier {\n    let useOpactityTransition: Bool\n    let isIdentity: Bool\n\n    func body(content: Content) -> some View {\n        content\n            .opacity(useOpactityTransition && !isIdentity ? 0 : 1)\n            .offset(x: !useOpactityTransition && !isIdentity ? 350 : 0)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Notifications/Views/NotificationPanelView.swift",
    "content": "//\n//  NotificationPanelView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 2/10/24.\n//\n\nimport SwiftUI\n\nstruct NotificationPanelView: View {\n    @EnvironmentObject private var workspace: WorkspaceDocument\n    @Environment(\\.controlActiveState)\n    private var controlActiveState\n\n    @ObservedObject private var notificationManager = NotificationManager.shared\n    @FocusState private var isFocused: Bool\n\n    // ID for the top anchor\n    private let topID = \"top\"\n\n    // Fixed width for notifications\n    private let notificationWidth: CGFloat = 320 // 300 + 10 padding on each side\n\n    @State private var hasOverflow: Bool = false\n    @State private var contentHeight: CGFloat = 0.0\n\n    private func updateOverflow(contentHeight: CGFloat, containerHeight: CGFloat) {\n        if !hasOverflow && contentHeight > containerHeight {\n            hasOverflow = true\n        } else if hasOverflow && contentHeight <= containerHeight {\n            hasOverflow = false\n        }\n    }\n\n    @ViewBuilder var notifications: some View {\n        let visibleNotifications = workspace.notificationPanel.activeNotifications.filter {\n            workspace.notificationPanel.isNotificationVisible($0)\n        }\n\n        VStack(spacing: 8) {\n            ForEach(visibleNotifications, id: \\.id) { notification in\n                NotificationBannerView(\n                    notification: notification,\n                    onDismiss: {\n                        workspace.notificationPanel.dismissNotification(notification)\n                    },\n                    onAction: {\n                        notification.action()\n                        if workspace.notificationPanel.isPresented {\n                            workspace.notificationPanel.toggleNotificationsVisibility()\n                            workspace.notificationPanel.dismissNotification(notification, disableAnimation: true)\n                        } else {\n                            workspace.notificationPanel.dismissNotification(notification)\n                        }\n                    }\n                )\n            }\n        }\n        .padding(10)\n        .animation(.easeInOut(duration: 0.3), value: visibleNotifications)\n    }\n\n    @ViewBuilder var notificationsWithScrollView: some View {\n        GeometryReader { geometry in\n            HStack {\n                Spacer()\n                ScrollViewReader { proxy in\n                    ScrollView(.vertical, showsIndicators: false) {\n                        VStack(alignment: .trailing, spacing: 0) {\n                            Color.clear\n                                .frame(height: 0)\n                                .id(topID)\n                                .background(\n                                    GeometryReader {\n                                        Color.clear.preference(\n                                            key: ViewOffsetKey.self,\n                                            value: -$0.frame(in: .named(\"scroll\")).origin.y\n                                        )\n                                    }\n                                )\n                                .onPreferenceChange(ViewOffsetKey.self) {\n                                    if $0 <= 0.0 && !workspace.notificationPanel.scrolledToTop {\n                                        workspace.notificationPanel.scrolledToTop = true\n                                    } else if $0 > 0.0 && workspace.notificationPanel.scrolledToTop {\n                                        workspace.notificationPanel.scrolledToTop = false\n                                    }\n                                }\n                            notifications\n                        }\n                        .background(\n                            GeometryReader { proxy in\n                                Color.clear.onChange(of: proxy.size.height) { _, newValue in\n                                    contentHeight = newValue\n                                    updateOverflow(contentHeight: newValue, containerHeight: geometry.size.height)\n                                }\n                            }\n                        )\n                    }\n                    .frame(maxWidth: notificationWidth, alignment: .trailing)\n                    .frame(height: min(geometry.size.height, contentHeight))\n                    .scrollDisabled(!hasOverflow)\n                    .coordinateSpace(name: \"scroll\")\n                    .onChange(of: isFocused) { _, newValue in\n                        workspace.notificationPanel.handleFocusChange(isFocused: newValue)\n                    }\n                    .onChange(of: geometry.size.height) { _, newValue in\n                        updateOverflow(contentHeight: contentHeight, containerHeight: newValue)\n                    }\n                    .onChange(of: workspace.notificationPanel.isPresented) { _, isPresented in\n                        if !isPresented && !workspace.notificationPanel.scrolledToTop {\n                            // If scrolled, delay scroll animation until after notifications are hidden\n                            DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {\n                                withAnimation(.easeOut(duration: 0.3)) {\n                                    proxy.scrollTo(topID, anchor: .top)\n                                }\n                            }\n                        }\n                    }\n                    .allowsHitTesting(\n                        workspace.notificationPanel.activeNotifications\n                            .contains { workspace.notificationPanel.isNotificationVisible($0) }\n                    )\n                }\n            }\n        }\n    }\n\n    var body: some View {\n        Group {\n            if #available(macOS 14.0, *) {\n                notificationsWithScrollView\n                    .scrollClipDisabled(true)\n                    .focusable()\n                    .focusEffectDisabled()\n                    .focused($isFocused)\n                    .onChange(of: workspace.notificationPanel.isPresented) { _, isPresented in\n                        if isPresented {\n                            isFocused = true\n                        }\n                    }\n                    .onChange(of: controlActiveState) { _, newState in\n                        if newState != .active && newState != .key && workspace.notificationPanel.isPresented {\n                            // Delay hiding notifications to match animation timing\n                            DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {\n                                workspace.notificationPanel.toggleNotificationsVisibility()\n                            }\n                        }\n                    }\n            } else {\n                notificationsWithScrollView\n            }\n        }\n        .opacity(controlActiveState == .active || controlActiveState == .key ? 1 : 0)\n        .offset(\n            x: (controlActiveState == .active || controlActiveState == .key) &&\n                (workspace.notificationPanel.isPresented || workspace.notificationPanel.scrolledToTop)\n                ? 0\n                : 350\n        )\n        .animation(.easeInOut(duration: 0.3), value: workspace.notificationPanel.isPresented)\n        .animation(.easeInOut(duration: 0.3), value: workspace.notificationPanel.scrolledToTop)\n        .animation(.easeInOut(duration: 0.2), value: controlActiveState)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Notifications/Views/NotificationToolbarItem.swift",
    "content": "//\n//  NotificationToolbarItem.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 2/10/24.\n//\n\nimport SwiftUI\n\nstruct NotificationToolbarItem: View {\n    @EnvironmentObject private var workspace: WorkspaceDocument\n    @ObservedObject private var notificationManager = NotificationManager.shared\n    @Environment(\\.controlActiveState)\n    private var controlActiveState\n\n    var body: some View {\n        let visibleNotifications = workspace.notificationPanel.visibleNotifications\n\n        if notificationManager.unreadCount > 0 || !visibleNotifications.isEmpty {\n            Button {\n                workspace.notificationPanel.toggleNotificationsVisibility()\n            } label: {\n                HStack(spacing: 4) {\n                    Image(systemName: \"bell.badge.fill\")\n                        .symbolRenderingMode(.palette)\n                        .foregroundStyle(controlActiveState == .inactive ? .secondary : Color.accentColor, .primary)\n                    Text(\"\\(notificationManager.unreadCount)\")\n                        .monospacedDigit()\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/OpenQuickly/ViewModels/OpenQuicklyViewModel.swift",
    "content": "//\n//  OpenQuicklyViewModel.swift\n//  CodeEditModules/QuickOpen\n//\n//  Created by Marco Carnevali on 05/04/22.\n//\n\nimport Combine\nimport Foundation\nimport CollectionConcurrencyKit\n\nfinal class OpenQuicklyViewModel: ObservableObject {\n    @Published var query: String = \"\"\n    @Published var searchResults: [SearchResult] = []\n\n    let fileURL: URL\n    var runningTask: Task<Void, Never>?\n\n    init(fileURL: URL) {\n        self.fileURL = fileURL\n    }\n\n    /// This is used to populate the ``OpenQuicklyListItemView`` view which shows the search results to the user.\n    ///\n    /// ``OpenQuicklyPreviewView`` also uses this to load the `fileUrl` for preview.\n    struct SearchResult: Identifiable, Hashable {\n        var id: String { fileURL.id }\n        let fileURL: URL\n        let matchedCharacters: [NSRange]\n\n        // This custom Hashable implementation prevents the highlighted\n        // selection from flickering when searching in 'Open Quickly'.\n        //\n        // See https://github.com/CodeEditApp/CodeEdit/pull/1790#issuecomment-2206832901\n        // for flickering visuals.\n        //\n        // Before commit 0e28b382f59184b7ebe5a7c3295afa3655b7d4e7, only the fileURL\n        // was retrieved from the search results and it worked as expected.\n        //\n        static func == (lhs: Self, rhs: Self) -> Bool { lhs.fileURL == rhs.fileURL }\n        func hash(into hasher: inout Hasher) { hasher.combine(fileURL) }\n    }\n\n    func fetchResults() {\n        let startTime = Date()\n        guard query != \"\" else {\n            searchResults = []\n            return\n        }\n\n        runningTask?.cancel()\n        runningTask = Task.detached(priority: .userInitiated) {\n            let enumerator = FileManager.default.enumerator(\n                at: self.fileURL,\n                includingPropertiesForKeys: [\n                    .isRegularFileKey\n                ],\n                options: [\n                    .skipsPackageDescendants\n                ]\n            )\n            if let filePaths = enumerator?.allObjects as? [URL] {\n                guard !Task.isCancelled else { return }\n                /// removes all filePaths which aren't regular files\n                let filteredFiles = filePaths.filter { url in\n                    do {\n                        let values = try url.resourceValues(forKeys: [.isRegularFileKey])\n                        return (values.isRegularFile ?? false)\n                    } catch {\n                        return false\n                    }\n                }\n\n                let fuzzySearchResults = await filteredFiles.fuzzySearch(\n                    query: self.query.trimmingCharacters(in: .whitespaces)\n                ).concurrentMap {\n                    SearchResult(\n                        fileURL: $0.item,\n                        matchedCharacters: $0.result.matchedParts\n                    )\n                }\n\n                guard !Task.isCancelled else { return }\n                await MainActor.run {\n                    self.searchResults = fuzzySearchResults\n                    print(\"Duration: \\(Date().timeIntervalSince(startTime))\")\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/OpenQuickly/Views/NSTableViewWrapper.swift",
    "content": "//\n//  NSTableViewWrapper.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 18/03/2023.\n//\n\nimport SwiftUI\nimport AppKit\n\nstruct NSTableViewWrapper<Content: View, Item: Identifiable & Hashable>: NSViewRepresentable {\n\n    var data: [Item]\n    var rowHeight: CGFloat = 50\n\n    @Binding var selection: Item?\n\n    var itemView: (Item) -> Content\n\n    class NonRespondingScrollView: NSScrollView {\n        override var acceptsFirstResponder: Bool { false }\n    }\n\n    class NonRespondingTableView: NSTableView {\n        override var acceptsFirstResponder: Bool { false }\n    }\n\n    func makeNSView(context: Context) -> NSScrollView {\n        let scrollView = NonRespondingScrollView()\n        scrollView.hasVerticalScroller = true\n        scrollView.verticalScroller?.controlSize = .mini\n\n        let tableView = NonRespondingTableView()\n        tableView.headerView = nil\n\n        let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(\"column\"))\n        column.width = tableView.frame.width\n\n        tableView.addTableColumn(column)\n        tableView.delegate = context.coordinator\n        tableView.dataSource = context.coordinator\n\n        scrollView.documentView = tableView\n\n        return scrollView\n    }\n\n    func updateNSView(_ nsView: NSScrollView, context: Context) {\n        context.coordinator.parent = self\n\n        if let view = nsView.documentView as? NSTableView {\n            view.reloadData()\n            if let selection, let item = data.firstIndex(of: selection) {\n                view.selectRowIndexes([item], byExtendingSelection: false)\n                view.scrollRowToVisible(item)\n            } else {\n                view.selectRowIndexes([], byExtendingSelection: false)\n            }\n        }\n    }\n\n    func makeCoordinator() -> Coordinator {\n        Coordinator(parent: self)\n    }\n\n    class Coordinator: NSObject, NSTableViewDelegate, NSTableViewDataSource {\n\n        var parent: NSTableViewWrapper\n\n        init(parent: NSTableViewWrapper) {\n            self.parent = parent\n        }\n\n        func numberOfRows(in tableView: NSTableView) -> Int {\n            return parent.data.count\n        }\n\n        class AlwaysActiveTableRowView: NSTableRowView {\n            override var isEmphasized: Bool {\n                get { true }\n                set { }\n            }\n        }\n\n        func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {\n            AlwaysActiveTableRowView()\n        }\n\n        func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {\n            parent.rowHeight\n        }\n\n        func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {\n            let view = NSHostingView(rootView: parent.itemView(parent.data[row]))\n            view.translatesAutoresizingMaskIntoConstraints = false\n\n            let cell = NSTableCellView()\n            cell.addSubview(view)\n\n            NSLayoutConstraint.activate([\n                .init(\n                    item: view,\n                    attribute: .centerY,\n                    relatedBy: .equal,\n                    toItem: cell,\n                    attribute: .centerY,\n                    multiplier: 1,\n                    constant: 0\n                ),\n                .init(\n                    item: view,\n                    attribute: .left,\n                    relatedBy: .equal,\n                    toItem: cell,\n                    attribute: .left,\n                    multiplier: 1,\n                    constant: 0\n                ),\n                .init(\n                    item: view,\n                    attribute: .right,\n                    relatedBy: .equal,\n                    toItem: cell,\n                    attribute: .right,\n                    multiplier: 1,\n                    constant: 0\n                )\n            ])\n\n            return cell\n        }\n\n        func tableViewSelectionDidChange(_ notification: Notification) {\n            if let view = notification.object as? NSTableView {\n                let newSelection = parent.data[safe: view.selectedRow]\n                if newSelection != parent.selection {\n                    parent.selection = newSelection\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/OpenQuickly/Views/OpenQuicklyListItemView.swift",
    "content": "//\n//  OpenQuicklyListItemView.swift\n//  CodeEditModules/QuickOpen\n//\n//  Created by Pavel Kasila on 20.03.22.\n//\n\nimport SwiftUI\n\nstruct OpenQuicklyListItemView: View {\n    private let baseDirectory: URL\n    private let searchResult: OpenQuicklyViewModel.SearchResult\n\n    init(\n        baseDirectory: URL,\n        searchResult: OpenQuicklyViewModel.SearchResult\n    ) {\n        self.baseDirectory = baseDirectory\n        self.searchResult = searchResult\n    }\n\n    var relativePathComponents: ArraySlice<String> {\n        return searchResult.fileURL.pathComponents.dropFirst(baseDirectory.pathComponents.count).dropLast()\n    }\n\n    var body: some View {\n        HStack(spacing: 8) {\n            Image(nsImage: NSWorkspace.shared.icon(forFile: searchResult.fileURL.path))\n                .resizable()\n                .aspectRatio(contentMode: .fit)\n                .frame(width: 24, height: 24)\n            VStack(alignment: .leading, spacing: 0) {\n                QuickSearchResultLabel(\n                    labelName: searchResult.fileURL.lastPathComponent,\n                    charactersToHighlight: searchResult.matchedCharacters\n                )\n                Text(relativePathComponents.joined(separator: \" ▸ \"))\n                    .font(.system(size: 10.5))\n                    .foregroundColor(.secondary)\n                    .lineLimit(1)\n                    .truncationMode(.middle)\n            }\n            .frame(maxWidth: .infinity, alignment: .leading)\n        }\n        .frame(maxWidth: .infinity)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/OpenQuickly/Views/OpenQuicklyPreviewView.swift",
    "content": "//\n//  OpenQuicklyPreviewView.swift\n//  CodeEditModules/QuickOpen\n//\n//  Created by Pavel Kasila on 20.03.22.\n//\n\nimport SwiftUI\n\nstruct OpenQuicklyPreviewView: View {\n\n    private let queue = DispatchQueue(label: \"app.codeedit.CodeEdit.quickOpen.preview\")\n    private let item: CEWorkspaceFile\n\n    @StateObject var editorInstance: EditorInstance\n    @StateObject var document: CodeFileDocument\n\n    @StateObject var undoRegistration: UndoManagerRegistration = UndoManagerRegistration()\n\n    init(item: CEWorkspaceFile) {\n        self.item = item\n        let doc = try? CodeFileDocument(\n            for: item.url,\n            withContentsOf: item.url,\n            ofType: item.contentType?.identifier ?? \"public.source-code\"\n        )\n        self._editorInstance = .init(wrappedValue: EditorInstance(workspace: nil, file: item))\n        self._document = .init(wrappedValue: doc ?? .init())\n    }\n\n    var body: some View {\n        if let utType = document.utType, utType.conforms(to: .text) {\n            CodeFileView(editorInstance: editorInstance, codeFile: document, isEditable: false)\n                .environmentObject(undoRegistration)\n        } else {\n            NonTextFileView(fileDocument: document)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/OpenQuickly/Views/OpenQuicklyView.swift",
    "content": "//\n//  OpenQuicklyView.swift\n//  CodeEditModules/QuickOpen\n//\n//  Created by Pavel Kasila on 20.03.22.\n//\n\nimport SwiftUI\n\nstruct OpenQuicklyView: View {\n    @EnvironmentObject private var workspace: WorkspaceDocument\n\n    private let onClose: () -> Void\n    private let openFile: (CEWorkspaceFile) -> Void\n\n    @ObservedObject private var openQuicklyViewModel: OpenQuicklyViewModel\n\n    @State private var selectedItem: CEWorkspaceFile?\n\n    init(\n        state: OpenQuicklyViewModel,\n        onClose: @escaping () -> Void,\n        openFile: @escaping (CEWorkspaceFile) -> Void\n    ) {\n        self.openQuicklyViewModel = state\n        self.onClose = onClose\n        self.openFile = openFile\n    }\n\n    var body: some View {\n        SearchPanelView(\n            title: \"Open Quickly\",\n            image: Image(systemName: \"magnifyingglass\"),\n            options: $openQuicklyViewModel.searchResults,\n            text: $openQuicklyViewModel.query,\n            optionRowHeight: 40\n        ) { searchResult in\n            OpenQuicklyListItemView(\n                baseDirectory: openQuicklyViewModel.fileURL,\n                searchResult: searchResult\n            )\n        } preview: { searchResult in\n            OpenQuicklyPreviewView(item: CEWorkspaceFile(url: searchResult.fileURL))\n        } onRowClick: { searchResult in\n            guard let file = workspace.workspaceFileManager?.getFile(\n                searchResult.fileURL.relativePath,\n                createIfNotFound: true\n            ) else {\n                return\n            }\n            openFile(file)\n            openQuicklyViewModel.query = \"\"\n            onClose()\n        } onClose: {\n            onClose()\n        }\n        .onReceive(openQuicklyViewModel.$query.debounce(for: 0.2, scheduler: DispatchQueue.main)) { _ in\n            openQuicklyViewModel.fetchResults()\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Search/Extensions/String+SafeOffset.swift",
    "content": "//\n//  String+SafeOffset.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/15/22.\n//\n\nimport Foundation\n\n/// Some safer alternative methods to ``String.\nextension String {\n    /// Safely returns an offset index in a string.\n    /// Use ``safeOffset(_:offsetBy:)`` to default to limiting to the start or end indexes.\n    /// - Parameters:\n    ///   - idx: The index to start at.\n    ///   - offsetBy: The number (of characters) to offset from the first index.\n    ///   - limitedBy: An index to limit the offset by.\n    /// - Returns: A `String.Index`\n    func safeOffset(_ idx: String.Index, offsetBy offset: Int, limitedBy: String.Index) -> String.Index? {\n        // This is the odd case this method solves. Swift's\n        // ``String.index(_:offsetBy:limitedBy:)``\n        // will crash if the given index is equal to the offset, and\n        // we try to go outside of the string's limits anyways.\n        if idx == limitedBy {\n            return nil\n        } else if offset < 0 {\n            // If the offset is going backwards, but the limit index\n            // is ahead in the string we return the original index.\n            if limitedBy > idx {\n                return idx\n            }\n\n            // Return the index offset by the given offset.\n            // If this index is nil we return the limit index.\n            return index(idx, offsetBy: offset, limitedBy: limitedBy)\n        } else if offset > 0 {\n            // If the offset is going forwards, but the limit index\n            // is behind in the string we return the original index.\n            if limitedBy < idx {\n                return idx\n            }\n\n            // Return the index offset by the given offset.\n            // If this index is nil we return the limit index.\n            return index(idx, offsetBy: offset, limitedBy: limitedBy)\n        } else {\n            // The offset is 0, so we return the limit index.\n            return limitedBy\n        }\n    }\n\n    /// Safely returns an offset index in a string.\n    /// This method will default to limiting to the start or end of the string.\n    /// See ``safeOffset(_:offsetBy:limitedBy:)`` for custom limit indexes.\n    /// - Parameters:\n    ///   - idx: The index to start at.\n    ///   - offsetBy: The number (of characters) to offset from the first index.\n    /// - Returns: A `String.Index`\n    func safeOffset(_ idx: String.Index, offsetBy offset: Int) -> String.Index? {\n        if offset < 0 {\n            return safeOffset(idx, offsetBy: offset, limitedBy: self.startIndex)\n        } else if offset > 0 {\n            return safeOffset(idx, offsetBy: offset, limitedBy: self.endIndex)\n        } else {\n            // If the offset is 0 we return the original index.\n            return idx\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Search/FuzzySearch/Collection+FuzzySearch.swift",
    "content": "//\n//  Collection+FuzzySearch.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 03.02.24.\n//\n\nimport Foundation\nimport CollectionConcurrencyKit\n\nextension Collection where Iterator.Element: FuzzySearchable {\n    /// Asynchronously performs a fuzzy search on a collection of elements conforming to FuzzySearchable.\n    ///\n    /// - Parameter query: The query string to match against the elements.\n    ///\n    /// - Returns: An array of tuples containing FuzzySearchMatchResult and the corresponding element.\n    ///\n    /// - Note: Because this is an extension on Collection and not only array,\n    /// you can also use this on sets.\n    func fuzzySearch(query: String) async -> [(result: FuzzySearchMatchResult, item: Iterator.Element)] {\n        return await concurrentMap {\n            (result: $0.fuzzyMatch(query: query), item: $0)\n        }.filter {\n            $0.result.weight > 0\n        }.sorted {\n            $0.result.weight > $1.result.weight\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Search/FuzzySearch/FuzzySearchModels.swift",
    "content": "//\n//  FuzzySearchModels.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 03.02.24.\n//\n\nimport Foundation\n\n/// FuzzySearchCharacters is used to normalise strings\nstruct FuzzySearchCharacter {\n    let content: String\n    // normalised content is referring to a string that is case- and accent-insensitive\n    let normalisedContent: String\n}\n\n/// FuzzySearchString is just made up by multiple characters, similar to a string, but also with normalised characters\nstruct FuzzySearchString {\n    var characters: [FuzzySearchCharacter]\n}\n\n/// FuzzySearchMatchResult represents an object that has undergone a fuzzy search using the fuzzyMatch function.\nstruct FuzzySearchMatchResult {\n    let weight: Int\n    let matchedParts: [NSRange]\n}\n"
  },
  {
    "path": "CodeEdit/Features/Search/FuzzySearch/FuzzySearchUIModel.swift",
    "content": "//\n//  FuzzySearchUIModel.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 8/14/25.\n//\n\nimport Foundation\nimport Combine\n\n@MainActor\nfinal class FuzzySearchUIModel<Element: FuzzySearchable>: ObservableObject {\n    @Published var items: [Element]?\n\n    private var allItems: [Element] = []\n    private var textStream: AsyncStream<String>\n    private var textStreamContinuation: AsyncStream<String>.Continuation\n    private var searchTask: Task<Void, Never>?\n\n    init(debounceTime: Duration = .milliseconds(50)) {\n        (textStream, textStreamContinuation) = AsyncStream<String>.makeStream()\n\n        searchTask = Task { [weak self] in\n            guard let self else { return }\n\n            for await text in textStream.debounce(for: debounceTime) {\n                await performSearch(query: text)\n            }\n        }\n    }\n\n    func searchTextUpdated(searchText: String, allItems: [Element]) {\n        self.allItems = allItems\n        textStreamContinuation.yield(searchText)\n    }\n\n    private func performSearch(query: String) async {\n        guard !query.isEmpty else {\n            items = nil\n            return\n        }\n\n        let results = await allItems.fuzzySearch(query: query)\n        items = results.map { $0.item }\n    }\n\n    deinit {\n        textStreamContinuation.finish()\n        searchTask?.cancel()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Search/FuzzySearch/FuzzySearchable.swift",
    "content": "//\n//  FuzzySearchable.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 03.02.24.\n//\n\nimport Foundation\n\n/// A protocol defining the requirements for an object that can be searched using fuzzy matching.\nprotocol FuzzySearchable {\n    var searchableString: String { get }\n\n    /// Performs a fuzzy search on the conforming object's searchable string.\n    ///\n    /// - Parameters:\n    ///   - query: The query string to match against the searchable content.\n    ///   - characters: The set of characters used for fuzzy matching.\n    ///\n    /// - Returns: A FuzzySearchMatchResult indicating the result of the fuzzy search.\n    func fuzzyMatch(query: String, characters: FuzzySearchString) -> FuzzySearchMatchResult\n}\n\nextension FuzzySearchable {\n    func fuzzyMatch(query: String, characters: FuzzySearchString) -> FuzzySearchMatchResult {\n        let compareString = characters.characters\n\n        let searchString = query.lowercased()\n\n        var totalScore = 0\n        var matchedParts = [NSRange]()\n\n        var patternIndex = 0\n        var currentScore = 0\n        var currentMatchedPart = NSRange(location: 0, length: 0)\n\n        for (index, character) in compareString.enumerated() {\n            if let prefixLength = searchString.lengthOfMatchingPrefix(prefix: character, startingAt: patternIndex) {\n                patternIndex += prefixLength\n                currentScore += 1\n                currentMatchedPart.length += 1\n            } else {\n                currentScore = 0\n                if currentMatchedPart.length != 0 {\n                    matchedParts.append(currentMatchedPart)\n                }\n                currentMatchedPart = NSRange(location: index + 1, length: 0)\n            }\n\n            totalScore += currentScore\n        }\n\n        if currentMatchedPart.length != 0 {\n            matchedParts.append(currentMatchedPart)\n        }\n\n        if searchString.count == matchedParts.reduce(0, { partialResult, range in\n            range.length + partialResult\n        }) {\n            return FuzzySearchMatchResult(weight: totalScore, matchedParts: matchedParts)\n        } else {\n            return FuzzySearchMatchResult(weight: 0, matchedParts: [])\n        }\n    }\n\n    /// Normalises the searchable string of the conforming object by converting its characters to ASCII representation.\n    /// The resulting FuzzySearchString contains both the original and normalised content of each character.\n    ///\n    /// - Returns: A FuzzySearchString\n    func normaliseString() -> FuzzySearchString {\n        return FuzzySearchString(characters: searchableString.normalise())\n    }\n\n    /// Performs a fuzzy search on the normalised content of the conforming object's searchable string.\n    ///\n    /// - Parameter query: The query string to match against the normalised searchable content.\n    ///\n    /// - Returns: A FuzzySearchMatchResult indicating the result of the fuzzy search.\n    func fuzzyMatch(query: String) -> FuzzySearchMatchResult {\n        let characters = normaliseString()\n\n        return fuzzyMatch(query: query, characters: characters)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Search/FuzzySearch/String+LengthOfMatchingPrefix.swift",
    "content": "//\n//  String+LengthOfMatchingPrefix.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 03.02.24.\n//\n\nimport Foundation\n\nextension String {\n    /// Returns the length of the matching prefix content or normalised content at the specified index.\n    ///\n    /// - Parameters:\n    ///   - prefix: The FuzzySearchCharacter whose content or normalised content to check for a prefix match.\n    ///   - index: The index from which to start searching for the prefix.\n    ///\n    /// - Returns: The length of the matching prefix, or nil if no match is found.\n    func lengthOfMatchingPrefix(prefix: FuzzySearchCharacter, startingAt index: Int) -> Int? {\n        guard let stringIndex = self.index(self.startIndex, offsetBy: index, limitedBy: self.endIndex) else {\n            return nil\n        }\n\n        let searchString = self.suffix(from: stringIndex)\n\n        for prefix in [prefix.content, prefix.normalisedContent] where searchString.hasPrefix(prefix) {\n            return prefix.count\n        }\n\n        return nil\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Search/FuzzySearch/String+Normalise.swift",
    "content": "//\n//  String+Normalise.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 03.02.24.\n//\n\nimport Foundation\n\nextension String {\n    /// Normalises the characters of the string by converting them to ASCII representation.\n    /// Each character is transformed into its ASCII equivalent, and the resulting array\n    /// of FuzzySearchCharacter objects contains both the original and normalised content.\n    ///\n    /// - Returns: An array of FuzzySearchCharacter objects representing the original and\n    /// normalised content of each character in the string.\n    func normalise() -> [FuzzySearchCharacter] {\n        return self.lowercased().map { char in\n            guard let data = String(char).data(using: .ascii, allowLossyConversion: true),\n                  let normalisedCharacter = String(data: data, encoding: .ascii) else {\n                return FuzzySearchCharacter(content: String(char), normalisedContent: String(char))\n            }\n\n            return FuzzySearchCharacter(content: String(char), normalisedContent: normalisedCharacter)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Search/Model/SearchModeModel.swift",
    "content": "//\n//  SearchModeModel.swift\n//  CodeEditModules/Search\n//\n//  Created by Ziyuan Zhao on 2022/3/22.\n//\n\nimport Foundation\n\n// TODO: DOCS (Ziyuan Zhao)\nstruct SearchModeModel: Hashable {\n    let title: String\n    let children: [SearchModeModel]\n    let needSelectionHighlight: Bool\n\n    static let Containing = SearchModeModel(title: \"Containing\", children: [], needSelectionHighlight: false)\n    static let MatchingWord = SearchModeModel(\n        title: \"Matching Word\",\n        children: [],\n        needSelectionHighlight: true\n    )\n    static let StartingWith = SearchModeModel(\n        title: \"Starting With\",\n        children: [],\n        needSelectionHighlight: true\n    )\n    static let EndingWith = SearchModeModel(title: \"Ending With\", children: [], needSelectionHighlight: true)\n\n    static let Text = SearchModeModel(\n        title: \"Text\",\n        children: [.Containing, .MatchingWord, .StartingWith, .EndingWith],\n        needSelectionHighlight: false\n    )\n    static let References = SearchModeModel(\n        title: \"References\",\n        children: [.Containing, .MatchingWord, .StartingWith, .EndingWith],\n        needSelectionHighlight: true\n    )\n    static let Definitions = SearchModeModel(\n        title: \"Definitions\",\n        children: [.Containing, .MatchingWord, .StartingWith, .EndingWith],\n        needSelectionHighlight: true\n    )\n    static let RegularExpression = SearchModeModel(\n        title: \"Regular Expression\",\n        children: [],\n        needSelectionHighlight: true\n    )\n    static let CallHierarchy = SearchModeModel(\n        title: \"Call Hierarchy\",\n        children: [],\n        needSelectionHighlight: true\n    )\n\n    static let Find = SearchModeModel(\n        title: \"Find\",\n        children: [.Text, .References, .Definitions, .RegularExpression, .CallHierarchy],\n        needSelectionHighlight: false\n    )\n    static let Replace = SearchModeModel(\n        title: \"Replace\",\n        children: [.Text, .RegularExpression],\n        needSelectionHighlight: true\n    )\n\n    static let TextMatchingModes: [SearchModeModel] = [.Containing, .MatchingWord, .StartingWith, .EndingWith]\n    static let FindModes: [SearchModeModel] = [\n        .Text,\n        .References,\n        .Definitions,\n        .RegularExpression,\n        .CallHierarchy\n    ]\n    static let ReplaceModes: [SearchModeModel] = [.Text, .RegularExpression]\n    static let SearchModes: [SearchModeModel] = [.Find, .Replace]\n}\n\nextension SearchModeModel: Equatable {\n    static func == (lhs: SearchModeModel, rhs: SearchModeModel) -> Bool {\n        lhs.title == rhs.title\n            && lhs.children == rhs.children\n            && lhs.needSelectionHighlight == rhs.needSelectionHighlight\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Search/Model/SearchResultMatchModel.swift",
    "content": "//\n//  SearchResultLineMatchModel.swift\n//  CodeEditModules/Search\n//\n//  Created by Khan Winter on 7/6/22.\n//\n\nimport Foundation\nimport Cocoa\n\n/// A struct for holding information about a search match.\nclass SearchResultMatchModel: Hashable, Identifiable {\n    init(\n        rangeWithinFile: Range<String.Index>,\n        file: CEWorkspaceFile,\n        lineContent: String,\n        keywordRange: Range<String.Index>\n    ) {\n        self.id = UUID()\n        self.file = file\n        self.rangeWithinFile = rangeWithinFile\n        self.lineContent = lineContent\n        self.keywordRange = keywordRange\n    }\n\n    var id: UUID\n    var file: CEWorkspaceFile\n    var rangeWithinFile: Range<String.Index>\n    var lineContent: String\n    var keywordRange: Range<String.Index>\n\n    static func == (lhs: SearchResultMatchModel, rhs: SearchResultMatchModel) -> Bool {\n        return lhs.id == rhs.id\n        && lhs.file == rhs.file\n        && lhs.rangeWithinFile == rhs.rangeWithinFile\n        && lhs.lineContent == rhs.lineContent\n        && lhs.keywordRange == rhs.keywordRange\n    }\n\n    func hash(into hasher: inout Hasher) {\n        hasher.combine(id)\n        hasher.combine(file)\n        hasher.combine(rangeWithinFile)\n        hasher.combine(lineContent)\n        hasher.combine(keywordRange)\n    }\n\n    /// Returns a formatted `NSAttributedString` with the search result bolded.\n    /// Will only return 60 characters before and after the matched result.\n    /// - Returns: The formatted `NSAttributedString`\n    func attributedLabel() -> NSAttributedString {\n        // By default `NSTextView` will ignore any paragraph wrapping set to the label when it's\n        // using an `NSAttributedString` so we need to set the wrap mode here.\n        let paragraphStyle = NSMutableParagraphStyle()\n        paragraphStyle.lineBreakMode = .byCharWrapping\n\n        let normalAttributes: [NSAttributedString.Key: Any] = [\n            .font: NSFont.systemFont(\n                ofSize: 13,\n                weight: .regular\n            ),\n            .foregroundColor: NSColor.secondaryLabelColor,\n            .paragraphStyle: paragraphStyle\n        ]\n        let boldAttributes: [NSAttributedString.Key: Any] = [\n            .font: NSFont.systemFont(\n                ofSize: 13,\n                weight: .bold\n            ),\n            .foregroundColor: NSColor.labelColor,\n            .paragraphStyle: paragraphStyle\n        ]\n\n        // Set up the search result string with the matched search in bold.\n        let prefix = String(lineContent[..<keywordRange.lowerBound])\n        let searchMatch = String(lineContent[keywordRange])\n        let postfix = String(lineContent[keywordRange.upperBound...])\n\n        let attributedString = NSMutableAttributedString(\n            string: prefix,\n            attributes: normalAttributes\n        )\n        attributedString.append(NSAttributedString(\n            string: searchMatch,\n            attributes: boldAttributes\n        ))\n        attributedString.append(NSAttributedString(\n            string: postfix,\n            attributes: normalAttributes\n        ))\n\n        return attributedString\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Search/Model/SearchResultModel.swift",
    "content": "//\n//  SearchResultModel.swift\n//  CodeEditModules/Search\n//\n//  Created by Ziyuan Zhao on 2022/3/22.\n//\n\nimport Foundation\n\n/// A struct for holding information about a file and any matches it may have for a search query.\nclass SearchResultModel: Hashable {\n\n    var file: CEWorkspaceFile\n    // The score represents how well the file matches the search query.\n    // The higher the score is, the better the file matches the search query.\n    // The score is assign by Search Kit.\n    var score: Float\n    var lineMatches: [SearchResultMatchModel]\n\n    init(\n        file: CEWorkspaceFile,\n        score: Float,\n        lineMatches: [SearchResultMatchModel] = []\n    ) {\n        self.file = file\n        self.score = score\n        self.lineMatches = lineMatches\n    }\n\n    static func == (lhs: SearchResultModel, rhs: SearchResultModel) -> Bool {\n        return lhs.file == rhs.file\n        && lhs.lineMatches == rhs.lineMatches\n    }\n\n    func hash(into hasher: inout Hasher) {\n        hasher.combine(file)\n        hasher.combine(lineMatches)\n    }\n\n}\n"
  },
  {
    "path": "CodeEdit/Features/Search/Views/QuickSearchResultLabel.swift",
    "content": "//\n//  QuickSearchResultLabel.swift\n//  CodeEdit\n//\n//  Created by Paul Ebose on 2024/7/2.\n//\n\nimport SwiftUI\n\n/// Implementation of command palette entity. While swiftui does not allow to use NSMutableAttributeStrings,\n/// the only way to fallback to UIKit and have NSViewRepresentable to be a bridge between UIKit and SwiftUI.\n/// Highlights currently entered text query\nstruct QuickSearchResultLabel: NSViewRepresentable {\n    let labelName: String\n    let charactersToHighlight: [NSRange]\n    let maximumNumberOfLines: Int = 1\n    var nsLabelName: NSAttributedString?\n\n    public func makeNSView(context: Context) -> some NSTextField {\n        let label = NSTextField(wrappingLabelWithString: labelName)\n        label.translatesAutoresizingMaskIntoConstraints = false\n        label.drawsBackground = false\n        label.textColor = .labelColor\n        label.isEditable = false\n        label.isSelectable = false\n        label.font = .labelFont(ofSize: 13)\n        label.allowsDefaultTighteningForTruncation = false\n        label.cell?.truncatesLastVisibleLine = true\n        label.cell?.wraps = true\n        label.maximumNumberOfLines = maximumNumberOfLines\n        label.attributedStringValue = nsLabelName ?? highlight()\n        return label\n    }\n\n    func highlight() -> NSAttributedString {\n        let attribText = NSMutableAttributedString(string: self.labelName)\n        for range in charactersToHighlight {\n            attribText.addAttribute(.foregroundColor, value: NSColor.controlTextColor, range: range)\n            attribText.addAttribute(.font, value: NSFont.boldSystemFont(ofSize: NSFont.systemFontSize), range: range)\n        }\n        return attribText\n    }\n\n    func updateNSView(_ nsView: NSViewType, context: Context) {\n        nsView.textColor = if nsLabelName == nil && charactersToHighlight.isEmpty {\n            .controlTextColor\n        } else {\n            .secondaryLabelColor\n        }\n        nsView.attributedStringValue = nsLabelName ?? highlight()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Models/AppSettings.swift",
    "content": "//\n//  AppSettings.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 12/04/2023.\n//\n\nimport Foundation\nimport SwiftUI\n\n@propertyWrapper\nstruct AppSettings<T>: DynamicProperty where T: Equatable {\n\n    var settings: Environment<T>\n\n    let keyPath: WritableKeyPath<SettingsData, T>\n\n    init(_ keyPath: WritableKeyPath<SettingsData, T>) {\n        self.keyPath = keyPath\n        let settingsKeyPath = (\\EnvironmentValues.settings).appending(path: keyPath)\n        self.settings = Environment(settingsKeyPath)\n    }\n\n    var wrappedValue: T {\n        get {\n            Settings.shared.preferences[keyPath: keyPath]\n        }\n        nonmutating set {\n            Settings.shared.preferences[keyPath: keyPath] = newValue\n        }\n    }\n\n    var projectedValue: Binding<T> {\n        Binding {\n            Settings.shared.preferences[keyPath: keyPath]\n        } set: {\n            Settings.shared.preferences[keyPath: keyPath] = $0\n        }\n    }\n}\n\nstruct SettingsDataEnvironmentKey: EnvironmentKey {\n    static var defaultValue: SettingsData = .init()\n}\n\nextension EnvironmentValues {\n    var settings: SettingsDataEnvironmentKey.Value {\n        get { self[SettingsDataEnvironmentKey.self] }\n        set { self[SettingsDataEnvironmentKey.self] = newValue }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Models/GlobPattern.swift",
    "content": "//\n//  GlobPattern.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/2/24.\n//\n\nimport Foundation\n\n/// A simple model that associates a UUID with a glob pattern string.\n///\n/// This type does not interpret or validate the glob pattern itself.\n/// It is simply an identifier (`id`) and the glob pattern string (`value`) associated with it.\nstruct GlobPattern: Identifiable, Hashable, Decodable, Encodable {\n    /// Ephemeral UUID used to uniquely identify this instance in the UI\n    var id = UUID()\n\n    /// The Glob Pattern string\n    var value: String\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Models/PageAndSettings.swift",
    "content": "//\n//  PageAndSettings.swift\n//  CodeEdit\n//\n//  Created by Raymond Vleeshouwer on 10/07/23.\n//\n\nimport Foundation\n\nstruct PageAndSettings: Identifiable, Equatable {\n    let id: UUID = UUID()\n    let page: SettingsPage\n    let settings: [SettingsPage]\n\n    init(_ page: SettingsPage) {\n        self.page = page\n        self.settings = SettingsData().propertiesOf(page.name)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Models/Settings.swift",
    "content": "//\n//  Settings.swift\n//  CodeEditModules/Settings\n//\n//  Created by Lukas Pistrol on 01.04.22.\n//\n\nimport Foundation\nimport SwiftUI\nimport Combine\n\n/// The Preferences View Model. Accessible via the singleton \"``SettingsModel/shared``\".\n///\n/// **Usage:**\n/// ```swift\n/// @StateObject\n/// private var prefs: SettingsModel = .shared\n/// ```\nfinal class Settings: ObservableObject {\n\n    /// The publicly available singleton instance of ``SettingsModel``\n    static let shared: Settings = .init()\n\n    private var storeTask: AnyCancellable!\n\n    private init() {\n        self.preferences = .init()\n        self.preferences = loadSettings()\n\n        self.storeTask = self.$preferences.throttle(for: 2, scheduler: RunLoop.main, latest: true).sink {\n            try? self.savePreferences($0)\n        }\n    }\n\n    static subscript<T>(_ path: WritableKeyPath<SettingsData, T>, suite: Settings = .shared) -> T {\n        get {\n            suite.preferences[keyPath: path]\n        }\n        set {\n            suite.preferences[keyPath: path] = newValue\n        }\n    }\n\n    /// Published instance of the ``Settings`` model.\n    ///\n    /// Changes are saved automatically.\n    @Published var preferences: SettingsData\n\n    /// Load and construct ``Settings`` model from\n    /// `~/Library/Application Support/CodeEdit/settings.json`\n    private func loadSettings() -> SettingsData {\n        if !filemanager.fileExists(atPath: settingsURL.path) {\n            try? filemanager.createDirectory(at: baseURL, withIntermediateDirectories: false)\n            return .init()\n        }\n\n        guard let json = try? Data(contentsOf: settingsURL),\n              let prefs = try? JSONDecoder().decode(SettingsData.self, from: json)\n        else {\n            return .init()\n        }\n        return prefs\n    }\n\n    /// Save``Settings`` model to\n    /// `~/Library/Application Support/CodeEdit/settings.json`\n    private func savePreferences(_ data: SettingsData) throws {\n        let data = try JSONEncoder().encode(data)\n        let json = try JSONSerialization.jsonObject(with: data)\n        let prettyJSON = try JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted])\n        try prettyJSON.write(to: settingsURL, options: .atomic)\n    }\n\n    /// Default instance of the `FileManager`\n    private let filemanager = FileManager.default\n\n    /// The base URL of settings.\n    ///\n    /// Points to `~/Library/Application Support/CodeEdit/`\n    internal var baseURL: URL {\n        filemanager\n            .homeDirectoryForCurrentUser\n            .appending(path: \"Library/Application Support/CodeEdit\", directoryHint: .isDirectory)\n    }\n\n    /// The URL of the `settings.json` settings file.\n    ///\n    /// Points to `~/Library/Application Support/CodeEdit/settings.json`\n    private var settingsURL: URL {\n        baseURL\n            .appending(path: \"settings\")\n            .appendingPathExtension(\"json\")\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Models/SettingsData.swift",
    "content": "//\n//  Settings.swift\n//  CodeEditModules/Settings\n//\n//  Created by Lukas Pistrol on 01.04.22.\n//\n\nimport SwiftUI\nimport Foundation\n\n/// # Settings\n///\n/// The model structure of settings for `CodeEdit`\n///\n/// A `JSON` representation is persisted in `~/Library/Application Support/CodeEdit/preference.json`.\n/// - Attention: Don't use `UserDefaults` for persisting user accessible settings.\n///  If a further setting is needed, extend the struct like ``GeneralSettings``,\n///  ``ThemeSettings``,  or ``TerminalSettings`` does.\n///\n/// - Note: Also make sure to implement the ``init(from:)`` initializer, decoding\n///  all properties with\n///  [`decodeIfPresent`](https://developer.apple.com/documentation/swift/keyeddecodingcontainer/2921389-decodeifpresent)\n///  and providing a default value. Otherwise all settings get overridden.\nstruct SettingsData: Codable, Hashable {\n\n    /// The general global settings\n    var general: GeneralSettings = .init()\n\n    /// The global settings for accounts\n    var accounts: AccountsSettings = .init()\n\n    /// The global settings for themes\n    var navigation: NavigationSettings = .init()\n\n    /// The global settings for themes\n    var theme: ThemeSettings = .init()\n\n    /// The global settings for text editing\n    var textEditing: TextEditingSettings = .init()\n\n    /// The global settings for the terminal emulator\n    var terminal: TerminalSettings = .init()\n\n    /// The global settings for source control\n    var sourceControl: SourceControlSettings = .init()\n\n    /// The global settings for keybindings\n    var keybindings: KeybindingsSettings = .init()\n\n    /// Search Settings\n    var search: SearchSettings = .init()\n\n    /// Language Server Settings\n    var languageServers: LanguageServerSettings = .init()\n\n    /// Developer settings for CodeEdit developers\n    var developerSettings: DeveloperSettings = .init()\n\n    /// Default initializer\n    init() {}\n\n    /// Explicit decoder init for setting default values when key is not present in `JSON`\n    init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.general = try container.decodeIfPresent(GeneralSettings.self, forKey: .general) ?? .init()\n        self.accounts = try container.decodeIfPresent(AccountsSettings.self, forKey: .accounts) ?? .init()\n        self.navigation = try container.decodeIfPresent(NavigationSettings.self, forKey: .navigation) ?? .init()\n        self.theme = try container.decodeIfPresent(ThemeSettings.self, forKey: .theme) ?? .init()\n        self.terminal = try container.decodeIfPresent(TerminalSettings.self, forKey: .terminal) ?? .init()\n        self.textEditing = try container.decodeIfPresent(TextEditingSettings.self, forKey: .textEditing) ?? .init()\n        self.search = try container.decodeIfPresent(SearchSettings.self, forKey: .search) ?? .init()\n        self.sourceControl = try container.decodeIfPresent(\n            SourceControlSettings.self,\n            forKey: .sourceControl\n        ) ?? .init()\n        self.keybindings = try container.decodeIfPresent(\n            KeybindingsSettings.self,\n            forKey: .keybindings\n        ) ?? .init()\n        self.languageServers = try container.decodeIfPresent(\n            LanguageServerSettings.self, forKey: .languageServers\n        ) ?? .init()\n        self.developerSettings = try container.decodeIfPresent(\n            DeveloperSettings.self, forKey: .developerSettings\n        ) ?? .init()\n    }\n\n    // swiftlint:disable cyclomatic_complexity\n    func propertiesOf(_ name: SettingsPage.Name) -> [SettingsPage] {\n        var settings: [SettingsPage] = []\n\n        switch name {\n        case .general:\n            general.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) }\n        case .accounts:\n            accounts.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) }\n        case .navigation:\n            navigation.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) }\n        case .theme:\n            theme.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) }\n        case .textEditing:\n            textEditing.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) }\n        case .terminal:\n            terminal.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) }\n        case .search:\n            search.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) }\n        case .sourceControl:\n            sourceControl.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) }\n        case .location:\n            LocationsSettings().searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) }\n        case .languageServers:\n            LanguageServerSettings().searchKeys.forEach {\n                settings.append(.init(name, isSetting: true, settingName: $0))\n            }\n        case .developer:\n            developerSettings.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) }\n        case .behavior: return [.init(name, settingName: \"Error\")]\n        case .components: return [.init(name, settingName: \"Error\")]\n        case .keybindings: return [.init(name, settingName: \"Error\")]\n        case .advanced: return [.init(name, settingName: \"Error\")]\n        }\n\n        return settings\n    }\n    // swiftlint:enable cyclomatic_complexity\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Models/SettingsInjector.swift",
    "content": "//\n//  SettingsInjector.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 28/04/2023.\n//\n\nimport SwiftUI\n\nstruct SettingsInjector<Content: View>: View {\n\n    @ObservedObject var settings = Settings.shared\n\n    @ViewBuilder var content: Content\n\n    var body: some View {\n        content\n            .environment(\\.settings, settings.preferences)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Models/SettingsPage.swift",
    "content": "//\n//  SettingsPage.swift\n//  CodeEdit\n//\n//  Created by Raymond Vleeshouwer on 30/03/23.\n//\n\nimport Foundation\nimport SwiftUI\n\n/// A struct for a settings page\nstruct SettingsPage: Hashable, Equatable, Identifiable {\n    /// A struct for a sidebar icon, with a base color and SF Symbol\n    enum IconResource: Equatable, Hashable {\n        case system(_ name: String)\n        case symbol(_ name: String)\n        case asset(_ name: String)\n    }\n\n    /// An enum of all the settings pages\n    enum Name: String {\n        case general = \"General\"\n        case accounts = \"Accounts\"\n        case behavior = \"Behaviors\"\n        case navigation = \"Navigation\"\n        case theme = \"Themes\"\n        case textEditing = \"Text Editing\"\n        case terminal = \"Terminal\"\n        case search = \"Search\"\n        case keybindings = \"Key Bindings\"\n        case sourceControl = \"Source Control\"\n        case components = \"Components\"\n        case location = \"Locations\"\n        case advanced = \"Advanced\"\n        case languageServers = \"Language Servers\"\n        case developer = \"Developer\"\n    }\n\n    let id: UUID = UUID()\n\n    let name: Name\n    let baseColor: Color?\n    let isSetting: Bool\n    let settingName: String\n    var nameString: LocalizedStringKey {\n        LocalizedStringKey(name.rawValue)\n    }\n    let icon: IconResource?\n\n    /// Default initializer\n    init(\n        _ name: Name,\n        baseColor: Color? = nil,\n        icon: IconResource? = nil,\n        isSetting: Bool = false,\n        settingName: String = \"\"\n    ) {\n        self.name = name\n        self.baseColor = baseColor\n        self.icon = icon\n        self.isSetting = isSetting\n        self.settingName = settingName\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Models/SettingsSearchResult.swift",
    "content": "//\n//  SettingsSearchResult.swift\n//  CodeEdit\n//\n//  Created by Raymond Vleeshouwer on 17/06/23.\n//\n\nimport SwiftUI\n\n// TODO: Extend this struct further to support setting \"flashing\"\nfinal class SettingsSearchResult: Identifiable {\n    let id: UUID = UUID()\n    let pageFound: Bool\n    let pages: [SettingsPage]\n\n    init(\n        pageFound: Bool,\n        pages: [SettingsPage]\n    ) {\n        self.pageFound = pageFound\n        self.pages = pages\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Models/SettingsSidebarFix.swift",
    "content": "//\n//  SettingsSidebarFix.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 13/04/2023.\n//\n\nimport AppKit\n\nextension NSSplitViewItem {\n    @objc fileprivate var canCollapseSwizzled: Bool {\n        if let check = self.viewController.view.window?.isSettingsWindow, check {\n            return false\n        }\n        return self.canCollapseSwizzled\n    }\n\n    static func swizzle() {\n        let origSelector = #selector(getter: NSSplitViewItem.canCollapse)\n        let swizzledSelector = #selector(getter: canCollapseSwizzled)\n        let originalMethodSet = class_getInstanceMethod(self as AnyClass, origSelector)\n        let swizzledMethodSet = class_getInstanceMethod(self as AnyClass, swizzledSelector)\n\n        method_exchangeImplementations(originalMethodSet!, swizzledMethodSet!)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/AccountsSettings/AccountSelectionView.swift",
    "content": "//\n//  AccoundSelectionView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 4/5/23.\n//\n\nimport SwiftUI\n\nstruct AccountSelectionView: View {\n    @Environment(\\.dismiss)\n    var dismiss\n\n    @Binding var selectedProvider: SourceControlAccount.Provider?\n\n    var gitProviders = SourceControlAccount.Provider.allCases\n\n    var body: some View {\n        VStack(spacing: 0) {\n            Form {\n                Section {\n                    VStack(alignment: .leading, spacing: 0) {\n                        ForEach(gitProviders, id: \\.self) { provider in\n                            AccountsSettingsProviderRow(\n                                name: provider.name,\n                                iconResource: provider.iconResource,\n                                action: {\n                                    selectedProvider = provider\n                                    dismiss()\n                                }\n                            )\n                            Divider()\n                        }\n                    }\n                    .padding(-10)\n                } footer: {\n                    HStack {\n                        Spacer()\n                        Button {\n                            dismiss()\n                        } label: {\n                            Text(\"Cancel\")\n                                .padding(.horizontal)\n                        }\n                        .buttonStyle(.borderedProminent)\n                        .controlSize(.large)\n                    }\n                    .padding(.top, 10)\n                }\n            }\n            .formStyle(.grouped)\n            .scrollDisabled(true)\n        }\n        .frame(width: 300)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsAccountLink.swift",
    "content": "//\n//  AccountsSettingsAccountLink.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 4/30/23.\n//\n\nimport SwiftUI\n\nstruct AccountsSettingsAccountLink: View {\n    @Binding var account: SourceControlAccount\n\n    init(_ account: Binding<SourceControlAccount>) {\n        _account = account\n    }\n\n    var body: some View {\n        NavigationLink(destination: AccountsSettingsDetailsView($account)) {\n            Label {\n                Text(account.description)\n                Text(account.name)\n                    .font(.footnote)\n                    .foregroundColor(.secondary)\n            } icon: {\n                FeatureIcon(image: Image(account.provider.iconResource), size: 26)\n                    .padding(.vertical, 2)\n                    .padding(.leading, 2)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift",
    "content": "//\n//  AccountsSettingsDetailView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 4/6/23.\n//\n\nimport SwiftUI\n\nstruct AccountsSettingsDetailsView: View {\n    @Environment(\\.dismiss)\n    private var dismiss\n    @AppSettings(\\.accounts.sourceControlAccounts.sshKey)\n    var sshKey\n    @AppSettings(\\.accounts.sourceControlAccounts.gitAccounts)\n    var gitAccounts\n    @Binding var account: SourceControlAccount\n\n    @State var currentAccount: SourceControlAccount\n    @State var deleteConfirmationIsPresented: Bool = false\n    @State var prevSshKey: String\n    @State var createSshKeyIsPresented: Bool = false\n\n    init(_ account: Binding<SourceControlAccount>) {\n        _account = account\n        _currentAccount = State(initialValue: account.wrappedValue)\n        _prevSshKey = State(initialValue: account.sshKey.wrappedValue)\n    }\n\n    /// Default instance of the `FileManager`\n    private let filemanager = FileManager.default\n\n    func isPrivateSSHKey(_ contents: String) -> Bool {\n        if contents.starts(with: \"-----BEGIN OPENSSH PRIVATE KEY-----\\n\") &&\n           contents.hasSuffix(\"\\n-----END OPENSSH PRIVATE KEY-----\\n\") {\n            return true\n        } else {\n            return false\n        }\n    }\n\n    func isPublicSSHKey(_ contents: String) -> Bool {\n        let sshKeyPattern = \"^ssh-(rsa|dss|ed25519)\\\\s+[A-Za-z0-9+/]+[=]{0,2}(\\\\s+.+)?$\"\n        do {\n            let regex = try NSRegularExpression(pattern: sshKeyPattern)\n            let range = NSRange(location: 0, length: contents.utf16.count)\n            return regex.firstMatch(in: contents, options: [], range: range) != nil\n        } catch {\n            print(\"Error creating regular expression: \\(error.localizedDescription)\")\n            return false\n        }\n    }\n\n    var body: some View {\n        SettingsForm {\n            Section {\n                LabeledContent(\"Account\") {\n                    Text(currentAccount.name)\n                }\n                TextField(\"Description\", text: $currentAccount.description)\n                if currentAccount.provider.baseURL == nil {\n                    TextField(\"Server\", text: $currentAccount.serverURL)\n                }\n            }\n\n            Section {\n                Picker(selection: $currentAccount.urlProtocol) {\n                    Text(\"HTTPS\")\n                        .tag(SourceControlAccount.URLProtocol.https)\n                    Text(\"SSH\")\n                        .tag(SourceControlAccount.URLProtocol.ssh)\n                } label: {\n                    Text(\"Clone Using\")\n                    Text(\"New repositories will be cloned from \\(currentAccount.provider.name)\"\n                         + \" using \\(currentAccount.urlProtocol.rawValue).\")\n                }\n                .pickerStyle(.radioGroup)\n                if currentAccount.urlProtocol == .ssh {\n                    Picker(\"SSH Key\", selection: $currentAccount.sshKey) {\n                        Text(\"None\")\n                            .tag(\"\")\n                        Divider()\n                        if let sshPath = FileManager.default.homeDirectoryForCurrentUser.appending(\n                            path: \".ssh\",\n                            directoryHint: .isDirectory\n                        ) as URL? {\n                            if let files = try? FileManager.default.contentsOfDirectory(\n                                atPath: sshPath.path\n                            ) {\n                                ForEach(files, id: \\.self) { filename in\n                                    let fileURL = sshPath.appending(path: filename)\n                                    if let contents = try? String(contentsOf: fileURL) {\n                                        if isPublicSSHKey(contents) {\n                                            Text(filename.replacingOccurrences(of: \".pub\", with: \"\"))\n                                                .tag(fileURL.path)\n                                        }\n                                    }\n                                }\n                                Divider()\n                            }\n                        }\n                        Text(\"Create New...\")\n                            .tag(\"CREATE_NEW\")\n                        Text(\"Choose...\")\n                            .tag(\"CHOOSE\")\n                    }\n                    .onReceive([currentAccount.sshKey].publisher.first()) { value in\n                        if value == \"CREATE_NEW\" {\n                            print(\"Create a new ssh key...\")\n                            createSshKeyIsPresented = true\n                            currentAccount.sshKey = prevSshKey\n                        } else if value == \"CHOOSE\" {\n                            print(\"Choose a ssh key...\")\n                            currentAccount.sshKey = prevSshKey\n                        } else {\n                            // TODO: Validate SSH key and check if it is uploaded to git provider.\n                            // If not provide button to do so\n                        }\n                        prevSshKey = currentAccount.sshKey\n                    }\n                    .sheet(isPresented: $createSshKeyIsPresented, content: { CreateSSHKeyView() })\n                }\n            } footer: {\n                HStack {\n                    Button(\"Delete Account...\") {\n                        deleteConfirmationIsPresented.toggle()\n                    }\n                    .alert(\n                        Text(\"Are you sure you want to delete the account “\\(account.description)”?\"),\n                        isPresented: $deleteConfirmationIsPresented\n                    ) {\n                        Button(\"OK\") {\n                            // Handle the account delete\n                            handleAccountDelete()\n                            dismiss()\n                        }\n                        Button(\"Cancel\") {\n                            // Handle the cancel, dismiss the alert\n                            deleteConfirmationIsPresented.toggle()\n                        }\n                    } message: {\n                        Text(\"Deleting this account will remove it from CodeEdit.\")\n                    }\n\n                    Spacer()\n                }\n                .padding(.top, 10)\n            }\n        }\n        .onChange(of: currentAccount) { _, newValue in\n            account = newValue\n        }\n        .navigationTitle(currentAccount.description)\n        .navigationBarBackButtonVisible()\n    }\n\n    private func handleAccountDelete() {\n        // Delete account by finding the position of the account and remove by position\n        if let gitAccount = gitAccounts.firstIndex(of: account) {\n            gitAccounts.remove(at: gitAccount)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsProviderRow.swift",
    "content": "//\n//  AccoundsSettingsAccountRow.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 4/5/23.\n//\n\nimport SwiftUI\n\nstruct AccountsSettingsProviderRow: View {\n    var name: String\n    var iconResource: ImageResource\n    var action: () -> Void\n\n    @State private var hovering = false\n    @State private var pressing = false\n\n    var body: some View {\n        HStack {\n            FeatureIcon(image: Image(iconResource), size: 28)\n            Text(name)\n            Spacer()\n            if hovering {\n                Image(systemName: \"plus\")\n                    .foregroundColor(Color(.tertiaryLabelColor))\n                    .padding(.horizontal, 5)\n            }\n        }\n        .frame(maxWidth: .infinity, alignment: .leading)\n        .padding(10)\n        .background(pressing ? Color(nsColor: .quaternaryLabelColor) : Color(nsColor: .clear))\n        .overlay(Color(.black).opacity(0.0001))\n        .onHover { hover in\n            hovering = hover\n        }\n        .pressAction {\n            pressing = true\n        } onRelease: {\n            pressing = false\n            action()\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsSigninView.swift",
    "content": "//\n//  AccountsSettingsSigninView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 4/5/23.\n//\n\nimport SwiftUI\n\nstruct AccountsSettingsSigninView: View {\n    @Environment(\\.dismiss)\n    var dismiss\n    @Environment(\\.openURL)\n    var createToken\n\n    var provider: SourceControlAccount.Provider\n    @Binding var addAccountSheetPresented: Bool\n\n    init(_ provider: SourceControlAccount.Provider, addAccountSheetPresented: Binding<Bool>) {\n        self.provider = provider\n        self._addAccountSheetPresented = addAccountSheetPresented\n    }\n\n    @State var server = \"\"\n    @State var username = \"\"\n    @State var personalAccessToken = \"\"\n\n    @State var signinErrorAlertIsPresented: Bool = false\n    @State var signinErrorDetail: String = \"\"\n\n    @AppSettings(\\.accounts.sourceControlAccounts.gitAccounts)\n    var gitAccounts\n\n    private let keychain = CodeEditKeychain()\n\n    var body: some View {\n        VStack(spacing: 0) {\n            Form {\n                Section(\n                    content: {\n                        if provider.baseURL == nil {\n                            VStack(alignment: .leading, spacing: 5) {\n                                Text(\"Server\")\n                                    .font(.caption3)\n                                    .foregroundColor(.secondary)\n                                TextField(\"\", text: $server, prompt: Text(\"https://git.example.com\"))\n                                    .labelsHidden()\n                            }\n                        }\n                        VStack(alignment: .leading, spacing: 5) {\n                            Text(\"Username\")\n                                .font(.caption3)\n                                .foregroundColor(.secondary)\n                            TextField(\"\", text: $username)\n                                .labelsHidden()\n                        }\n                        VStack(alignment: .leading, spacing: 5) {\n                            Text(\"Personal Access Token\")\n                                .font(.caption3)\n                                .foregroundColor(.secondary)\n                            SecureField(\"\", text: $personalAccessToken)\n                                .labelsHidden()\n                         }\n                    },\n                    header: {\n                        VStack(alignment: .center, spacing: 10) {\n                            FeatureIcon(image: Image(provider.iconResource), size: 52)\n                                .padding(.top, 5)\n                            Text(\"Sign in to \\(provider.name)\")\n                                .multilineTextAlignment(.center)\n                        }\n                        .frame(maxWidth: .infinity)\n                    },\n                    footer: {\n                        VStack(alignment: .leading, spacing: 5) {\n                            if provider == .github {\n                                Text(\"\\(provider.name) personal access tokens must have these scopes set:\")\n                                    .font(.system(size: 10.5))\n                                    .foregroundColor(.secondary)\n                                    .multilineTextAlignment(.leading)\n                                HStack(alignment: .center) {\n                                    Spacer()\n                                    VStack(alignment: .leading) {\n                                        HStack(spacing: 2.5) {\n                                            Image(systemName: \"checkmark\")\n                                                .font(.system(size: 10.5, weight: .semibold))\n                                            Text(\"admin:public _key\")\n                                                .font(.system(size: 10.5))\n                                        }\n                                        HStack(spacing: 2.5) {\n                                            Image(systemName: \"checkmark\")\n                                                .font(.system(size: 10.5, weight: .semibold))\n                                            Text(\"write:discussion\")\n                                                .font(.system(size: 10.5))\n                                        }\n                                        HStack(spacing: 2.5) {\n                                            Image(systemName: \"checkmark\")\n                                                .font(.system(size: 10.5, weight: .semibold))\n                                            Text(\"repo\")\n                                                .font(.system(size: 10.5))\n                                        }\n                                        HStack(spacing: 2.5) {\n                                            Image(systemName: \"checkmark\")\n                                                .font(.system(size: 10.5, weight: .semibold))\n                                            Text(\"user\")\n                                                .font(.system(size: 10.5))\n                                        }\n                                    }\n                                    Spacer()\n                                }\n                                .foregroundColor(.secondary)\n                            }\n                            Button {\n                                createToken(provider.authHelpURL)\n                            } label: {\n                                if provider.authType == .password {\n                                    Text(\"Create a Password on \\(provider.name)\")\n                                        .font(.system(size: 10.5))\n                                } else {\n                                    Text(\"Create a Token on \\(provider.name)\")\n                                        .font(.system(size: 10.5))\n                                }\n                            }\n                            .buttonStyle(.link)\n                            .frame(maxWidth: .infinity, alignment: .leading)\n                        }\n                        .frame(maxWidth: .infinity)\n                    }\n                )\n            }\n            .formStyle(.grouped)\n            .scrollDisabled(true)\n            .onSubmit {\n                signin()\n            }\n            HStack {\n                Button {\n                    addAccountSheetPresented.toggle()\n                    dismiss()\n                } label: {\n                    Text(\"Cancel\")\n                        .frame(maxWidth: .infinity)\n                }\n                .controlSize(.large)\n                .frame(maxWidth: .infinity)\n\n                Button {\n                    signin()\n                } label: {\n                    Text(\"Sign In\")\n                        .frame(maxWidth: .infinity)\n                }\n                .disabled(username.isEmpty || personalAccessToken.isEmpty)\n                .buttonStyle(.borderedProminent)\n                .controlSize(.large)\n                .alert(\n                    Text(\"Unable to add account “\\(username)”\"),\n                    isPresented: $signinErrorAlertIsPresented\n                ) {\n                    Button(\"OK\") {\n                        signinErrorAlertIsPresented.toggle()\n                    }\n                } message: {\n                    Text(signinErrorDetail)\n                }\n            }\n            .padding(.horizontal)\n            .padding(.bottom)\n        }\n        .frame(width: 300)\n    }\n\n    private func signin() {\n        if gitAccounts.contains(\n            where: {\n                $0.serverURL == provider.baseURL?.absoluteString ?? server &&\n                $0.name.lowercased() == username.lowercased()\n            }\n        ) {\n            // Show alert when adding a duplicated account\n            signinErrorDetail = \"Account with the same username and provider already exists!\"\n            signinErrorAlertIsPresented.toggle()\n        } else {\n            let configURL = provider.apiURL?.absoluteString ?? server\n            switch provider {\n            case .github, .githubEnterprise:\n                let config = GitHubTokenConfiguration(personalAccessToken, url: configURL)\n                GitHubAccount(config).me { response in\n                    switch response {\n                    case .success:\n                        handleGitRequestSuccess()\n                    case .failure(let error):\n                        handleGitRequestFailed(error)\n                    }\n                }\n            case .gitlab, .gitlabSelfHosted:\n                let config = GitLabTokenConfiguration(personalAccessToken, url: configURL)\n                GitLabAccount(config).me { response in\n                    switch response {\n                    case .success:\n                        handleGitRequestSuccess()\n                    case .failure(let error):\n                        handleGitRequestFailed(error)\n                    }\n                }\n            default:\n                print(\"do nothing\")\n            }\n        }\n    }\n\n    private func handleGitRequestSuccess() {\n        let providerLink = provider.baseURL?.absoluteString ?? server\n\n        self.gitAccounts.append(\n            SourceControlAccount(\n                id: \"\\(providerLink)_\\(username.lowercased())\",\n                name: username,\n                description: provider.name,\n                provider: provider,\n                serverURL: providerLink,\n                urlProtocol: .https,\n                sshKey: \"\",\n                isTokenValid: true\n            )\n        )\n\n        keychain.set(personalAccessToken, forKey: \"github_\\(username)_enterprise\")\n        dismiss()\n    }\n\n    private func handleGitRequestFailed(_ error: Error) {\n        print(\"git auth failure: \\(error)\")\n        // Show alert if error encountered while requesting signin\n        switch error._code {\n        case -1009:\n            signinErrorDetail = error.localizedDescription\n        case 401:\n            signinErrorDetail = \"Authentication Failed\"\n        case 403:\n            signinErrorDetail = \"API Access Forbidden\"\n        default:\n            signinErrorDetail = \"Unknown Error\"\n        }\n        signinErrorAlertIsPresented.toggle()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsView.swift",
    "content": "//\n//  AccountSettingsView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 4/4/23.\n//\n\nimport SwiftUI\n\nstruct AccountsSettingsView: View {\n    @AppSettings(\\.accounts.sourceControlAccounts.gitAccounts)\n    var gitAccounts\n\n    @State private var addAccountSheetPresented: Bool = false\n    @State private var selectedProvider: SourceControlAccount.Provider?\n\n    var body: some View {\n        SettingsForm {\n            Section {\n                if $gitAccounts.isEmpty {\n                    Text(\"No accounts\")\n                        .foregroundColor(.secondary)\n                        .frame(maxWidth: .infinity, alignment: .center)\n                } else {\n                    ForEach($gitAccounts, id: \\.self) { $account in\n                        AccountsSettingsAccountLink($account)\n                    }\n                }\n            } footer: {\n                HStack {\n                    Spacer()\n                    Button(\"Add Account...\") { addAccountSheetPresented.toggle() }\n                    .sheet(isPresented: $addAccountSheetPresented, content: {\n                        AccountSelectionView(selectedProvider: $selectedProvider)\n                    })\n                    .sheet(item: $selectedProvider, content: { provider in\n                        switch provider {\n                        case .github, .githubEnterprise, .gitlab, .gitlabSelfHosted:\n                            AccountsSettingsSigninView(provider, addAccountSheetPresented: $addAccountSheetPresented)\n                        default:\n                            implementationNeeded\n                        }\n                    })\n                }\n                .padding(.top, 10)\n            }\n        }\n    }\n\n    private var implementationNeeded: some View {\n        VStack(spacing: 20) {\n            Text(\"This git client is currently not supported.\")\n            HStack {\n                Button(\"Close\") {\n                    addAccountSheetPresented.toggle()\n                    selectedProvider = nil\n                }\n                .buttonStyle(.borderedProminent)\n            }\n            .frame(maxWidth: .infinity, alignment: .trailing)\n        }\n        .padding(20)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/AccountsSettings/CreateSSHKeyView.swift",
    "content": "//\n//  CreateSSHKeyView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 4/28/23.\n//\n\nimport SwiftUI\n\nstruct CreateSSHKeyView: View {\n    @Environment(\\.dismiss)\n    private var dismiss\n\n    enum KeyType: String, CaseIterable {\n        case ed25519 = \"ED25519\"\n        case ecdsa = \"ECDSA\"\n        case rsa = \"RSA\"\n        case dsa = \"DSA\"\n    }\n\n    @State var selectedKeyType: KeyType = .ed25519\n    @State var passphrase: String = \"\"\n    @State var confirmPassphrase: String = \"\"\n\n    var body: some View {\n        VStack {\n            Form {\n                Section(\"Create SSH key\") {\n                    Picker(\"Key Type\", selection: $selectedKeyType) {\n                        Text(KeyType.ed25519.rawValue)\n                            .tag(KeyType.ed25519)\n                        Text(KeyType.ecdsa.rawValue)\n                            .tag(KeyType.ecdsa)\n                        Divider()\n                        Group {\n                            Text(KeyType.rsa.rawValue) + Text(\" (less secure)\").foregroundColor(.secondary)\n                        }\n                        .tag(KeyType.rsa)\n                        Group {\n                            Text(KeyType.dsa.rawValue) + Text(\" (less secure)\").foregroundColor(.secondary)\n                        }\n                        .tag(KeyType.dsa)\n                    }\n                    SecureField(\"Passphrase\", text: $passphrase)\n                    if !passphrase.isEmpty {\n                        SecureField(\"Confirm Passphrase\", text: $confirmPassphrase)\n                    }\n                }\n            }\n            .formStyle(.grouped)\n            .fixedSize()\n            .scrollDisabled(true)\n            HStack {\n                Spacer()\n                Button(\"Cancel\") {\n                    dismiss()\n                }\n                Button(\"Create\") {\n                    // create the ssh key\n                    dismiss()\n                }\n                .buttonStyle(.borderedProminent)\n            }\n            .padding(.horizontal, 20)\n            .padding(.bottom, 20)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/AccountsSettings/Models/AccountsSettings.swift",
    "content": "//\n//  AccountsPreferences.swift\n//  CodeEditModules/Settings\n//\n//  Created by Nanashi Li on 2022/04/08.\n//\n\nimport Foundation\n\nextension SettingsData {\n\n    /// The global settings for source control accounts\n    struct AccountsSettings: Codable, Hashable, SearchableSettingsPage {\n        /// The list of git accounts the user has saved\n        var sourceControlAccounts: GitAccounts = .init()\n\n        /// The search keys\n        var searchKeys: [String] {\n            [\n                \"Accounts\",\n                \"Delete Account...\",\n                \"Add Account...\"\n            ]\n            .map { NSLocalizedString($0, comment: \"\") }\n        }\n\n        /// Default initializer\n        init() {}\n\n        /// Explicit decoder init for setting default values when key is not present in `JSON`\n        init(from decoder: Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.sourceControlAccounts = try container.decodeIfPresent(\n                GitAccounts.self,\n                forKey: .sourceControlAccounts\n            ) ?? .init()\n        }\n    }\n\n    struct GitAccounts: Codable, Hashable {\n        /// This id will store the account name as the identifiable\n        var gitAccounts: [SourceControlAccount] = []\n\n        var sshKey: String = \"\"\n        /// Default initializer\n        init() {}\n        /// Explicit decoder init for setting default values when key is not present in `JSON`\n        init(from decoder: Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.gitAccounts = try container.decodeIfPresent([SourceControlAccount].self, forKey: .gitAccounts) ?? []\n            self.sshKey = try container.decodeIfPresent(String.self, forKey: .sshKey) ?? \"\"\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/AccountsSettings/Models/SourceControlAccount.swift",
    "content": "//\n//  SourceControlAccount.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 4/6/23.\n//\n\nimport SwiftUI\n\nstruct SourceControlAccount: Codable, Identifiable, Hashable {\n\n    var id: String\n    var name: String\n    var description: String\n    var provider: Provider\n    var serverURL: String\n    // TODO: Should we use an enum instead of a boolean here:\n    // If true we use the HTTP protocol else if false we use SSH\n    var urlProtocol: URLProtocol\n    var sshKey: String\n    var isTokenValid: Bool\n\n    enum URLProtocol: String, Codable, CaseIterable {\n        case https = \"HTTPS\"\n        case ssh = \"SSH\"\n    }\n\n    enum Provider: Codable, CaseIterable, Identifiable {\n        case bitbucketCloud\n        case bitbucketServer\n        case github\n        case githubEnterprise\n        case gitlab\n        case gitlabSelfHosted\n\n        var id: String {\n            switch self {\n            case .bitbucketCloud:\n                return \"bitbucketCloud\"\n            case .bitbucketServer:\n                return \"bitbucketServer\"\n            case .github:\n                return \"github\"\n            case .githubEnterprise:\n                return \"githubEnterprise\"\n            case .gitlab:\n                return \"gitlab\"\n            case .gitlabSelfHosted:\n                return \"gitlabSelfHosted\"\n            }\n        }\n\n        var name: String {\n            switch self {\n            case .bitbucketCloud:\n                return \"BitBucket Cloud\"\n            case .bitbucketServer:\n                return \"BitBucket Server\"\n            case .github:\n                return \"GitHub\"\n            case .githubEnterprise:\n                return \"GitHub Enterprise\"\n            case .gitlab:\n                return \"GitLab\"\n            case .gitlabSelfHosted:\n                return \"GitLab Self-hosted\"\n            }\n        }\n\n        var baseURL: URL? {\n            switch self {\n            case .bitbucketCloud:\n                return URL(string: \"https://www.bitbucket.com/\")!\n            case .bitbucketServer:\n                return nil\n            case .github:\n                return URL(string: \"https://www.github.com/\")!\n            case .githubEnterprise:\n                return nil\n            case .gitlab:\n                return URL(string: \"https://www.gitlab.com/\")!\n            case .gitlabSelfHosted:\n                return nil\n            }\n        }\n\n        var apiURL: URL? {\n            switch self {\n            case .bitbucketCloud:\n                return URL(string: \"https://api.bitbucket.org/2.0/\")!\n            case .bitbucketServer:\n                return nil\n            case .github:\n                return URL(string: \"https://api.github.com/\")!\n            case .githubEnterprise:\n                return nil\n            case .gitlab:\n                return URL(string: \"https://gitlab.com/api/v4/\")!\n            case .gitlabSelfHosted:\n                return nil\n            }\n        }\n\n        var iconResource: ImageResource {\n            switch self {\n            case .bitbucketCloud, .bitbucketServer:\n                return .bitBucketIcon\n            case .github, .githubEnterprise:\n                return .gitHubIcon\n            case .gitlab, .gitlabSelfHosted:\n                return .gitLabIcon\n            }\n        }\n\n        var authHelpURL: URL {\n            switch self {\n            case .bitbucketCloud:\n                return URL(string: \"https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/\")!\n            case .bitbucketServer:\n                return URL(string:\n                    \"https://confluence.atlassian.com/bitbucketserver/personal-access-tokens-939515499.html\")!\n            case .github:\n                return URL(string: \"https://github.com/settings/tokens/new\")!\n            case .githubEnterprise:\n                return URL(string: \"https://github.com/settings/tokens/new\")!\n            case .gitlab:\n                return URL(string: \"https://gitlab.com/-/profile/personal_access_tokens\")!\n            case .gitlabSelfHosted:\n                return URL(string: \"https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html\")!\n            }\n        }\n\n        var authType: AuthType {\n            switch self {\n            case .bitbucketCloud:\n                return .password\n            case .bitbucketServer:\n                return .token\n            case .github:\n                return .token\n            case .githubEnterprise:\n                return .token\n            case .gitlab:\n                return .token\n            case .gitlabSelfHosted:\n                return .token\n            }\n        }\n    }\n\n    enum AuthType {\n        case token\n        case password\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/DeveloperSettings/DeveloperSettingsView.swift",
    "content": "//\n//  DeveloperSettingsView.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 5/16/24.\n//\n\nimport SwiftUI\nimport LanguageServerProtocol\n\n/// A view that implements the Developer settings section\nstruct DeveloperSettingsView: View {\n    @AppSettings(\\.developerSettings.lspBinaries)\n    var lspBinaries\n\n    @AppSettings(\\.developerSettings.showInternalDevelopmentInspector)\n    var showInternalDevelopmentInspector\n\n    var body: some View {\n        SettingsForm {\n            Section {\n                Toggle(\"Show Internal Development Inspector\", isOn: $showInternalDevelopmentInspector)\n            }\n\n            Section {\n                KeyValueTable(\n                    items: $lspBinaries,\n                    validKeys: LanguageIdentifier.allCases.map { $0.rawValue },\n                    keyColumnName: \"Language\",\n                    valueColumnName: \"Language Server Path\",\n                    newItemInstruction: \"Add a language server\"\n                ) {\n                    Text(\"Add a language server\")\n                    Text(\n                        \"Specify the absolute path to your LSP binary and its associated language.\"\n                    )\n                } actionBarTrailing: {\n                    EmptyView()\n                }\n                .frame(minHeight: 96)\n            } header: {\n                Text(\"LSP Binaries\")\n                Text(\"Specify the language and the absolute path to the language server binary.\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/DeveloperSettings/Models/DeveloperSettings.swift",
    "content": "//\n//  DeveloperSettings.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 5/15/24.\n//\n\nimport Foundation\n\nextension SettingsData {\n    struct DeveloperSettings: Codable, Hashable, SearchableSettingsPage {\n\n        /// The search keys\n        var searchKeys: [String] {\n            [\n                \"Developer\",\n                \"Language Server Protocol\",\n                \"LSP Binaries\",\n                \"Show Internal Development Inspector\"\n            ]\n            .map { NSLocalizedString($0, comment: \"\") }\n        }\n\n        /// A dictionary that stores a file type and a path to an LSP binary\n        var lspBinaries: [String: String] = [:]\n\n        /// Toggle for showing the internal development inspector\n        var showInternalDevelopmentInspector: Bool = false\n\n        /// Default initializer\n        init() {}\n\n        /// Explicit decoder init for setting default values when key is not present in `JSON`\n        init(from decoder: Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n\n            self.lspBinaries = try container.decodeIfPresent(\n                [String: String].self,\n                forKey: .lspBinaries\n            ) ?? [:]\n\n            self.showInternalDevelopmentInspector = try container.decodeIfPresent(\n                Bool.self,\n                forKey: .showInternalDevelopmentInspector\n            ) ?? false\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/Extensions/LanguageServerInstallView.swift",
    "content": "//\n//  LanguageServerInstallView.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 8/14/25.\n//\n\nimport SwiftUI\n\n/// A view for initiating a package install and monitoring progress.\nstruct LanguageServerInstallView: View {\n    @Environment(\\.dismiss)\n    var dismiss\n    @EnvironmentObject private var registryManager: RegistryManager\n\n    @ObservedObject var operation: PackageManagerInstallOperation\n\n    var body: some View {\n        VStack(spacing: 0) {\n            formContent\n            Divider()\n            footer\n        }\n        .constrainHeightToWindow()\n        .alert(\n            \"Confirm Step\",\n            isPresented: Binding(get: { operation.waitingForConfirmation != nil }, set: { _ in }),\n            presenting: operation.waitingForConfirmation\n        ) { _ in\n            Button(\"Cancel\") {\n                registryManager.cancelInstallation()\n            }\n            Button(\"Continue\") {\n                operation.confirmCurrentStep()\n            }\n        } message: { confirmationMessage in\n            Text(confirmationMessage)\n        }\n    }\n\n    @ViewBuilder private var formContent: some View {\n        Form {\n            packageInfoSection\n            errorSection\n            if operation.runningState == .running || operation.runningState == .complete {\n                progressSection\n                outputSection\n            } else {\n                notInstalledSection\n            }\n        }\n        .formStyle(.grouped)\n    }\n\n    @ViewBuilder private var footer: some View {\n        HStack {\n            Spacer()\n            switch operation.runningState {\n            case .none:\n                Button {\n                    dismiss()\n                } label: {\n                    Text(\"Cancel\")\n                }\n                .buttonStyle(.bordered)\n                Button {\n                    do {\n                        try registryManager.startInstallation(operation: operation)\n                    } catch {\n                        // Display the error\n                        NSAlert(error: error).runModal()\n                    }\n                } label: {\n                    Text(\"Install\")\n                }\n                .buttonStyle(.borderedProminent)\n            case .running:\n                Button {\n                    registryManager.cancelInstallation()\n                    dismiss()\n                } label: {\n                    Text(\"Cancel\")\n                        .frame(minWidth: 56)\n                }\n                .buttonStyle(.bordered)\n            case .complete:\n                Button {\n                    dismiss()\n                } label: {\n                    Text(\"Continue\")\n                        .frame(minWidth: 56)\n                }\n                .buttonStyle(.borderedProminent)\n            }\n        }\n        .padding()\n    }\n\n    @ViewBuilder private var packageInfoSection: some View {\n        Section {\n            LabeledContent(\"Installing Package\", value: operation.package.sanitizedName)\n            LabeledContent(\"Homepage\") {\n                sourceButton.cursor(.pointingHand)\n            }\n            VStack(alignment: .leading, spacing: 6) {\n                Text(\"Description\")\n                Text(operation.package.sanitizedDescription)\n                    .multilineTextAlignment(.leading)\n                    .foregroundColor(.secondary)\n                    .labelsHidden()\n                    .textSelection(.enabled)\n            }\n        }\n    }\n\n    @ViewBuilder private var errorSection: some View {\n        if let error = operation.error {\n            Section {\n                HStack(spacing: 4) {\n                    Image(systemName: \"exclamationmark.octagon.fill\").foregroundColor(.red)\n                    Text(\"Error Occurred\")\n                }\n                .font(.title3)\n                ErrorDescriptionLabel(error: error)\n            }\n        }\n    }\n\n    @ViewBuilder private var sourceButton: some View {\n        if #available(macOS 14.0, *) {\n            Button(operation.package.homepagePretty) {\n                guard let homepage = operation.package.homepageURL else { return }\n                NSWorkspace.shared.open(homepage)\n            }\n            .buttonStyle(.plain)\n            .foregroundColor(Color(NSColor.linkColor))\n            .focusEffectDisabled()\n        } else {\n            Button(operation.package.homepagePretty) {\n                guard let homepage = operation.package.homepageURL else { return }\n                NSWorkspace.shared.open(homepage)\n            }\n            .buttonStyle(.plain)\n            .foregroundColor(Color(NSColor.linkColor))\n        }\n    }\n\n    @ViewBuilder private var progressSection: some View {\n        Section {\n            LabeledContent(\"Step\") {\n                if registryManager.installedLanguageServers[operation.package.name] != nil {\n                    HStack(spacing: 4) {\n                        Image(systemName: \"checkmark.circle.fill\")\n                            .foregroundColor(.green)\n                        Text(\"Successfully Installed\")\n                            .foregroundStyle(.primary)\n                    }\n                } else if operation.error != nil {\n                    Text(\"Error Occurred\")\n                } else {\n                    Text(operation.currentStep?.name ?? \"\")\n                }\n            }\n            ProgressView(operation.progress)\n                .progressViewStyle(.linear)\n        }\n    }\n\n    @ViewBuilder private var outputSection: some View {\n        Section {\n            ScrollViewReader { proxy in\n                ScrollView {\n                    LazyVStack(spacing: 2) {\n                        ForEach(operation.accumulatedOutput) { line in\n                            VStack {\n                                if line.isStepDivider && line != operation.accumulatedOutput.first {\n                                    Divider()\n                                }\n                                HStack(alignment: .firstTextBaseline, spacing: 6) {\n                                    ZStack {\n                                        if let idx = line.outputIdx {\n                                            Text(String(idx))\n                                                .font(.caption2.monospaced())\n                                                .foregroundStyle(.tertiary)\n                                        }\n                                        Text(String(10)) // Placeholder for spacing\n                                            .font(.caption2.monospaced())\n                                            .foregroundStyle(.tertiary)\n                                            .opacity(0.0)\n                                    }\n                                    Text(line.contents)\n                                        .font(.caption.monospaced())\n                                        .foregroundStyle(line.isStepDivider ? .primary : .secondary)\n                                        .textSelection(.enabled)\n                                    Spacer(minLength: 0)\n                                }\n                            }\n                            .tag(line.id)\n                            .id(line.id)\n                        }\n                    }\n                }\n                .onReceive(operation.$accumulatedOutput) { output in\n                    DispatchQueue.main.async {\n                        withAnimation(.linear(duration: 0.1)) {\n                            proxy.scrollTo(output.last?.id)\n                        }\n                    }\n                }\n            }\n        }\n        .frame(height: 200)\n    }\n\n    @ViewBuilder private var notInstalledSection: some View {\n        Section {\n            if let method = operation.package.installMethod {\n                LabeledContent(\"Install Method\", value: method.installerDescription)\n                    .textSelection(.enabled)\n                if let packageDescription = method.packageDescription {\n                    LabeledContent(\"Package\", value: packageDescription)\n                        .textSelection(.enabled)\n                }\n            } else {\n                LabeledContent(\"Installer\", value: \"Unknown\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/Extensions/LanguageServerRowView.swift",
    "content": "//\n//  LanguageServerRowView.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/2/25.\n//\n\nimport SwiftUI\n\nprivate let iconSize: CGFloat = 26\n\nstruct LanguageServerRowView: View, Equatable {\n    let package: RegistryItem\n    let onCancel: (() -> Void)\n    let onInstall: (() async -> Void)\n\n    private var isInstalled: Bool {\n        registryManager.installedLanguageServers[package.name] != nil\n    }\n    private var isEnabled: Bool {\n        registryManager.installedLanguageServers[package.name]?.isEnabled ?? false\n    }\n\n    @State private var isHovering: Bool = false\n    @State private var showingRemovalConfirmation = false\n    @State private var isRemoving = false\n    @State private var removalError: Error?\n    @State private var showingRemovalError = false\n\n    @State private var showMore: Bool = false\n\n    @EnvironmentObject var registryManager: RegistryManager\n\n    init(\n        package: RegistryItem,\n        onCancel: @escaping (() -> Void),\n        onInstall: @escaping () async -> Void\n    ) {\n        self.package = package\n        self.onCancel = onCancel\n        self.onInstall = onInstall\n    }\n\n    var body: some View {\n        HStack {\n            Label {\n                VStack(alignment: .leading) {\n                    Text(package.sanitizedName)\n\n                    ZStack(alignment: .leadingLastTextBaseline) {\n                        VStack(alignment: .leading) {\n                            Text(package.sanitizedDescription)\n                                .font(.footnote)\n                                .foregroundColor(.secondary)\n                                .lineLimit(showMore ? nil : 1)\n                                .truncationMode(.tail)\n                            if showMore {\n                                Button(package.homepagePretty) {\n                                    guard let url = package.homepageURL else { return }\n                                    NSWorkspace.shared.open(url)\n                                }\n                                .buttonStyle(.plain)\n                                .foregroundColor(Color(NSColor.linkColor))\n                                .font(.footnote)\n                                .cursor(.pointingHand)\n                                if let installerName = package.installMethod?.packageManagerType?.rawValue {\n                                    Text(\"Install using \\(installerName)\")\n                                        .font(.footnote)\n                                        .foregroundColor(.secondary)\n                                }\n                            }\n                        }\n                        if isHovering {\n                            HStack {\n                                Spacer()\n                                Button {\n                                    showMore.toggle()\n                                } label: {\n                                    Text(showMore ? \"Show Less\" : \"Show More\")\n                                        .font(.footnote)\n                                }\n                                .buttonStyle(.plain)\n                                .background(\n                                    Rectangle()\n                                        .inset(by: -2)\n                                        .fill(.clear)\n                                        .background(Color(NSColor.windowBackgroundColor))\n                                )\n                            }\n                        }\n                    }\n                }\n            } icon: {\n                letterIcon()\n            }\n            .opacity(isInstalled && !isEnabled ? 0.5 : 1.0)\n\n            Spacer()\n\n            installationButton()\n        }\n        .onHover { hovering in\n            isHovering = hovering\n        }\n        .alert(\"Remove \\(package.sanitizedName)?\", isPresented: $showingRemovalConfirmation) {\n            Button(\"Cancel\", role: .cancel) { }\n            Button(\"Remove\", role: .destructive) {\n                removeLanguageServer()\n            }\n        } message: {\n            Text(\"Are you sure you want to remove this language server? This action cannot be undone.\")\n        }\n        .alert(\"Removal Failed\", isPresented: $showingRemovalError) {\n            Button(\"OK\", role: .cancel) { }\n        } message: {\n            Text(removalError?.localizedDescription ?? \"An unknown error occurred\")\n        }\n    }\n\n    @ViewBuilder\n    private func installationButton() -> some View {\n        if isInstalled {\n            installedRow()\n        } else if registryManager.runningInstall?.package.name == package.name {\n            isInstallingRow()\n        } else if isHovering {\n            isHoveringRow()\n        }\n    }\n\n    @ViewBuilder\n    private func installedRow() -> some View {\n        HStack {\n            if isRemoving {\n                CECircularProgressView()\n                    .frame(width: 20, height: 20)\n            } else if isHovering {\n                Button {\n                    showingRemovalConfirmation = true\n                } label: {\n                    Text(\"Remove\")\n                }\n            }\n            Toggle(\n                \"\",\n                isOn: Binding(\n                    get: { isEnabled },\n                    set: { registryManager.setPackageEnabled(packageName: package.name, enabled: $0) }\n                )\n            )\n            .toggleStyle(.switch)\n            .controlSize(.small)\n            .labelsHidden()\n        }\n    }\n\n    @ViewBuilder\n    private func isInstallingRow() -> some View {\n        HStack {\n            ZStack {\n                CECircularProgressView()\n                    .frame(width: 20, height: 20)\n\n                Button {\n                    onCancel()\n                } label: {\n                    Image(systemName: \"stop.fill\")\n                        .font(.system(size: 8))\n                        .foregroundColor(.blue)\n                }\n                .buttonStyle(.plain)\n                .contentShape(Rectangle())\n            }\n        }\n    }\n\n    @ViewBuilder\n    private func failedRow() -> some View {\n        Button {\n            Task {\n                await onInstall()\n            }\n        } label: {\n            Text(\"Retry\")\n                .foregroundColor(.red)\n        }\n    }\n\n    @ViewBuilder\n    private func isHoveringRow() -> some View {\n        Button {\n            Task {\n                await onInstall()\n            }\n        } label: {\n            Text(\"Install\")\n        }\n        .disabled(registryManager.isInstalling)\n    }\n\n    @ViewBuilder\n    private func letterIcon() -> some View {\n        RoundedRectangle(cornerRadius: iconSize / 4, style: .continuous)\n            .fill(background)\n            .overlay {\n                Text(String(package.sanitizedName.first ?? Character(\"\")))\n                    .font(.system(size: iconSize * 0.65))\n                    .foregroundColor(.primary)\n            }\n            .clipShape(RoundedRectangle(cornerRadius: iconSize / 4, style: .continuous))\n            .shadow(\n                color: Color(NSColor.black).opacity(0.25),\n                radius: iconSize / 40,\n                y: iconSize / 40\n            )\n            .frame(width: iconSize, height: iconSize)\n    }\n\n    private func removeLanguageServer() {\n        isRemoving = true\n        Task {\n            do {\n                try await registryManager.removeLanguageServer(packageName: package.name)\n                await MainActor.run {\n                    isRemoving = false\n                }\n            } catch {\n                await MainActor.run {\n                    isRemoving = false\n                    removalError = error\n                    showingRemovalError = true\n                }\n            }\n        }\n    }\n\n    private var background: AnyShapeStyle {\n        let colors: [Color] = [\n            .blue, .green, .orange, .red, .purple, .pink, .teal, .yellow, .indigo, .cyan\n        ]\n        let hashValue = abs(package.sanitizedName.hash) % colors.count\n        return AnyShapeStyle(colors[hashValue].gradient)\n    }\n\n    static func == (lhs: LanguageServerRowView, rhs: LanguageServerRowView) -> Bool {\n        lhs.package.name == rhs.package.name\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/Extensions/LanguageServersView.swift",
    "content": "//\n//  ExtensionsSettingsView.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/2/25.\n//\n\nimport SwiftUI\n\n/// Displays a searchable list of packages from the ``RegistryManager``.\nstruct LanguageServersView: View {\n    @StateObject var registryManager: RegistryManager = .shared\n    @StateObject private var searchModel = FuzzySearchUIModel<RegistryItem>()\n    @State private var searchText: String = \"\"\n    @State private var selectedInstall: PackageManagerInstallOperation?\n\n    @State private var showingInfoPanel = false\n\n    var body: some View {\n        Group {\n            SettingsForm {\n                if registryManager.isDownloadingRegistry {\n                    HStack {\n                        Spacer()\n                        ProgressView()\n                            .controlSize(.small)\n                        Spacer()\n                    }\n                }\n\n                Section {\n                    List(searchModel.items ?? registryManager.registryItems, id: \\.name) { item in\n                        LanguageServerRowView(\n                            package: item,\n                            onCancel: {\n                                registryManager.cancelInstallation()\n                            },\n                            onInstall: { [item] in\n                                do {\n                                    selectedInstall = try registryManager.installOperation(package: item)\n                                } catch {\n                                    // Display the error\n                                    NSAlert(error: error).runModal()\n                                }\n                            }\n                        )\n                        .listRowInsets(EdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8))\n                    }\n                    .searchable(text: $searchText)\n                    .onChange(of: searchText) { _, newValue in\n                        searchModel.searchTextUpdated(searchText: newValue, allItems: registryManager.registryItems)\n                    }\n                } header: {\n                    Label(\n                        \"Warning: Language server installation is experimental. Use at your own risk.\",\n                        systemImage: \"exclamationmark.triangle.fill\"\n                    )\n                }\n            }\n            .sheet(item: $selectedInstall) { operation in\n                LanguageServerInstallView(operation: operation)\n            }\n        }\n        .environmentObject(registryManager)\n    }\n\n    private func getInfoString() -> AttributedString {\n        let string = \"CodeEdit makes use of the Mason Registry for language server installation. To install a package, \"\n        + \"CodeEdit uses the package manager directed by the Mason Registry, and installs a copy of \"\n        + \"the language server in Application Support.\\n\\n\"\n        + \"Language server installation is still experimental, there may be bugs and expect this flow \"\n        + \"to change over time.\"\n\n        var attrString = AttributedString(string)\n\n        if let linkRange = attrString.range(of: \"Mason Registry\") {\n            attrString[linkRange].link = URL(string: \"https://mason-registry.dev/\")\n            attrString[linkRange].foregroundColor = NSColor.linkColor\n        }\n\n        return attrString\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/Extensions/Models/LanguageServerSettings.swift",
    "content": "//\n//  LanguageServerSettings.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/2/25.\n//\n\nimport Foundation\n\nextension SettingsData {\n    struct LanguageServerSettings: Codable, Hashable, SearchableSettingsPage {\n\n        /// The search keys\n        var searchKeys: [String] {\n            [\n                \"Language Servers\",\n                \"LSP Binaries\",\n                \"Linters\",\n                \"Formatters\",\n                \"Debug Protocol\",\n                \"DAP\",\n            ]\n            .map { NSLocalizedString($0, comment: \"\") }\n        }\n\n        /// Stores the currently installed language servers. The key is the name of the language server.\n        var installedLanguageServers: [String: InstalledLanguageServer] = [:]\n\n        /// Default initializer\n        init() {\n            self.installedLanguageServers = [:]\n        }\n\n        /// Explicit decoder init for setting default values when key is not present in `JSON`\n        init(from decoder: Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.installedLanguageServers = try container.decodeIfPresent(\n                [String: InstalledLanguageServer].self,\n                forKey: .installedLanguageServers\n            ) ?? [:]\n        }\n    }\n\n    struct InstalledLanguageServer: Codable, Hashable {\n        let packageName: String\n        var isEnabled: Bool\n        let version: String\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/GeneralSettings/GeneralSettingsView.swift",
    "content": "//\n//  GeneralSettingsView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 4/1/23.\n//\n\nimport SwiftUI\n\n/// A view that implements the `General` settings page\nstruct GeneralSettingsView: View {\n    private let inputWidth: Double = 160\n    private let textEditorWidth: Double = 220\n    private let textEditorHeight: Double = 30\n\n    @EnvironmentObject var updater: SoftwareUpdater\n    @FocusState private var focusedField: UUID?\n\n    @AppSettings(\\.general)\n    var settings\n\n    @State private var openInCodeEdit: Bool = true\n\n    init() {\n        guard let defaults = UserDefaults.init(\n            suiteName: \"app.codeedit.CodeEdit.shared\"\n        ) else {\n            print(\"Failed to get/init shared defaults\")\n            return\n        }\n\n        self.openInCodeEdit = defaults.bool(forKey: \"enableOpenInCE\")\n    }\n\n    var body: some View {\n        SettingsForm {\n            Section {\n                appearance\n                fileIconStyle\n                showEditorJumpBar\n                dimEditorsWithoutFocus\n                navigatorTabBarPosition\n                inspectorTabBarPosition\n            }\n            Section {\n                showIssues\n                showLiveIssues\n            }\n            Section {\n                autoSave\n                revealFileOnFocusChangeToggle\n                reopenBehavior\n                afterWindowsCloseBehaviour\n                fileExtensions\n            }\n            Section {\n                projectNavigatorSize\n                findNavigatorDetail\n                issueNavigatorDetail\n            }\n            Section {\n                openInCodeEditToggle\n                shellCommand\n                dialogWarnings\n\n            }\n            Section {\n                updateChecker\n                autoUpdateToggle\n                // TODO: Uncomment when production build is released.\n                // prereleaseToggle\n            }\n        }\n    }\n}\n\n/// The extension of the view with all the preferences\nprivate extension GeneralSettingsView {\n    var appearance: some View {\n        Picker(\"Appearance\", selection: $settings.appAppearance) {\n            Text(\"System\")\n                .tag(SettingsData.Appearances.system)\n            Divider()\n            Text(\"Light\")\n                .tag(SettingsData.Appearances.light)\n            Text(\"Dark\")\n                .tag(SettingsData.Appearances.dark)\n        }\n        .onChange(of: settings.appAppearance) { _, tag in\n            tag.applyAppearance()\n        }\n    }\n\n    // TODO: Implement reflecting Show Issues preference and remove disabled modifier\n    var showIssues: some View {\n        Picker(\"Show Issues\", selection: $settings.showIssues) {\n            Text(\"Show Inline\")\n                .tag(SettingsData.Issues.inline)\n            Text(\"Show Minimized\")\n                .tag(SettingsData.Issues.minimized)\n        }\n    }\n\n    var showLiveIssues: some View {\n        Toggle(\"Show Live Issues\", isOn: $settings.showLiveIssues)\n    }\n\n    var showEditorJumpBar: some View {\n        Toggle(\"Show Jump Bar\", isOn: $settings.showEditorJumpBar)\n    }\n\n    var dimEditorsWithoutFocus: some View {\n        Toggle(\"Dim editors without focus\", isOn: $settings.dimEditorsWithoutFocus)\n    }\n\n    var fileExtensions: some View {\n        Group {\n            Picker(\"File Extensions\", selection: $settings.fileExtensionsVisibility) {\n                Text(\"Hide all\")\n                    .tag(SettingsData.FileExtensionsVisibility.hideAll)\n                Text(\"Show all\")\n                    .tag(SettingsData.FileExtensionsVisibility.showAll)\n                Divider()\n                Text(\"Show only\")\n                    .tag(SettingsData.FileExtensionsVisibility.showOnly)\n                Text(\"Hide only\")\n                    .tag(SettingsData.FileExtensionsVisibility.hideOnly)\n            }\n            if case .showOnly = settings.fileExtensionsVisibility {\n                TextField(\"\", text: $settings.shownFileExtensions.string, axis: .vertical)\n                    .labelsHidden()\n                    .lineLimit(1...3)\n            }\n            if case .hideOnly = settings.fileExtensionsVisibility {\n                TextField(\"\", text: $settings.hiddenFileExtensions.string, axis: .vertical)\n                    .labelsHidden()\n                    .lineLimit(1...3)\n            }\n        }\n    }\n\n    var fileIconStyle: some View {\n        Picker(\"File Icon Style\", selection: $settings.fileIconStyle) {\n            Text(\"Color\")\n                .tag(SettingsData.FileIconStyle.color)\n            Text(\"Monochrome\")\n                .tag(SettingsData.FileIconStyle.monochrome)\n        }\n        .pickerStyle(.radioGroup)\n    }\n\n    var navigatorTabBarPosition: some View {\n        Picker(\"Navigator Tab Bar Position\", selection: $settings.navigatorTabBarPosition) {\n            Text(\"Top\")\n                .tag(SettingsData.SidebarTabBarPosition.top)\n            Text(\"Side\")\n                .tag(SettingsData.SidebarTabBarPosition.side)\n        }\n        .pickerStyle(.radioGroup)\n    }\n\n    var inspectorTabBarPosition: some View {\n        Picker(\"Inspector Tab Bar Position\", selection: $settings.inspectorTabBarPosition) {\n            Text(\"Top\")\n                .tag(SettingsData.SidebarTabBarPosition.top)\n            Text(\"Side\")\n                .tag(SettingsData.SidebarTabBarPosition.side)\n        }\n        .pickerStyle(.radioGroup)\n    }\n\n    var reopenBehavior: some View {\n        Picker(\"Reopen Behavior\", selection: $settings.reopenBehavior) {\n            Text(\"Welcome Screen\")\n                .tag(SettingsData.ReopenBehavior.welcome)\n            Divider()\n            Text(\"Open Panel\")\n                .tag(SettingsData.ReopenBehavior.openPanel)\n            Text(\"New Document\")\n                .tag(SettingsData.ReopenBehavior.newDocument)\n        }\n    }\n\n    var afterWindowsCloseBehaviour: some View {\n        Picker(\n            \"After the last window is closed\",\n            selection: $settings.reopenWindowAfterClose\n        ) {\n            Text(\"Do nothing\")\n                .tag(SettingsData.ReopenWindowBehavior.doNothing)\n            Divider()\n            Text(\"Show Welcome Window\")\n                .tag(SettingsData.ReopenWindowBehavior.showWelcomeWindow)\n            Text(\"Quit\")\n                .tag(SettingsData.ReopenWindowBehavior.quit)\n        }\n    }\n\n    var projectNavigatorSize: some View {\n        Picker(\"Project Navigator Size\", selection: $settings.projectNavigatorSize) {\n            Text(\"Small\")\n                .tag(SettingsData.ProjectNavigatorSize.small)\n            Text(\"Medium\")\n                .tag(SettingsData.ProjectNavigatorSize.medium)\n            Text(\"Large\")\n                .tag(SettingsData.ProjectNavigatorSize.large)\n        }\n    }\n\n    var findNavigatorDetail: some View {\n        Picker(\"Find Navigator Detail\", selection: $settings.findNavigatorDetail) {\n            ForEach(SettingsData.NavigatorDetail.allCases, id: \\.self) { tag in\n                Text(tag.label).tag(tag)\n            }\n        }\n    }\n\n    // TODO: Implement reflecting Issue Navigator Detail preference and remove disabled modifier\n    var issueNavigatorDetail: some View {\n        Picker(\"Issue Navigator Detail\", selection: $settings.issueNavigatorDetail) {\n            ForEach(SettingsData.NavigatorDetail.allCases, id: \\.self) { tag in\n                Text(tag.label).tag(tag)\n            }\n        }\n        .disabled(true)\n    }\n\n    // TODO: Implement reset for Don't Ask Me warnings Button and remove disabled modifier\n    var dialogWarnings: some View {\n        LabeledContent(\"Dialog Warnings\") {\n            Button(action: {\n            }, label: {\n                Text(\"Reset \\\"Don't Ask Me\\\" Warnings\")\n            })\n            .buttonStyle(.bordered)\n        }\n        .disabled(true)\n    }\n\n    var shellCommand: some View {\n        LabeledContent(\"'codeedit' Shell Command\") {\n            Button(action: installShellCommand, label: {\n                Text(\"Install\")\n            })\n            .disabled(true)\n            .buttonStyle(.bordered)\n        }\n    }\n\n    func installShellCommand() {\n        do {\n            let url = Bundle.main.url(forResource: \"codeedit\", withExtension: nil, subdirectory: \"Resources\")\n            let destination = \"/usr/local/bin/codeedit\"\n\n            if FileManager.default.fileExists(atPath: destination) {\n                try FileManager.default.removeItem(atPath: destination)\n            }\n\n            guard let shellUrl = url?.path else {\n                print(\"Failed to get URL to shell command\")\n                return\n            }\n\n            NSWorkspace.shared.requestAuthorization(to: .createSymbolicLink) { auth, error in\n                guard let auth, error == nil else {\n                    fallbackShellInstallation(commandPath: shellUrl, destinationPath: destination)\n                    return\n                }\n\n                do {\n                    try FileManager(authorization: auth).createSymbolicLink(\n                        atPath: destination, withDestinationPath: shellUrl\n                    )\n                } catch {\n                    fallbackShellInstallation(commandPath: shellUrl, destinationPath: destination)\n                }\n            }\n        } catch {\n            print(error)\n        }\n    }\n\n    var updateChecker: some View {\n        Section {\n            LabeledContent {\n                Button(\"Check Now\") {\n                    updater.checkForUpdates()\n                }\n            } label: {\n                Text(\"Check for updates\")\n                Text(\"Last checked: \\(lastUpdatedString)\")\n\n            }\n        }\n    }\n\n    var autoUpdateToggle: some View {\n        Toggle(\"Automatically check for app updates\", isOn: $updater.automaticallyChecksForUpdates)\n    }\n\n    var prereleaseToggle: some View {\n        Toggle(\"Include pre-release versions\", isOn: $updater.includePrereleaseVersions)\n    }\n\n    var autoSave: some View {\n        Toggle(\"Automatically save changes to disk\", isOn: $settings.isAutoSaveOn)\n    }\n\n    // MARK: - Preference Views\n\n    private var lastUpdatedString: String {\n        if let lastUpdatedDate = updater.lastUpdateCheckDate {\n            return Self.formatter.string(from: lastUpdatedDate)\n        } else {\n            return \"Never\"\n        }\n    }\n\n    private static func configure<Subject>(_ subject: Subject, configuration: (inout Subject) -> Void) -> Subject {\n        var copy = subject\n        configuration(&copy)\n        return copy\n    }\n\n    func fallbackShellInstallation(commandPath: String, destinationPath: String) {\n        let cmd = [\n            \"osascript\",\n            \"-e\",\n            \"\\\"do shell script \\\\\\\"mkdir -p /usr/local/bin && ln -sf \\'\\(commandPath)\\' \\'\\(destinationPath)\\'\\\\\\\"\\\"\",\n            \"with administrator privileges\"\n        ]\n\n        let cmdStr = cmd.joined(separator: \" \")\n\n        let task = Process()\n        let pipe = Pipe()\n\n        task.standardOutput = pipe\n        task.standardError = pipe\n        task.arguments = [\"-c\", cmdStr]\n        task.executableURL = URL(fileURLWithPath: \"/bin/zsh\")\n        task.standardInput = nil\n\n        do {\n            try task.run()\n        } catch {\n            print(error)\n        }\n    }\n\n    var openInCodeEditToggle: some View {\n        Toggle(\"Show “Open With CodeEdit” option in Finder\", isOn: $openInCodeEdit)\n            .onChange(of: openInCodeEdit) { _, newValue in\n                guard let defaults = UserDefaults.init(\n                    suiteName: \"app.codeedit.CodeEdit.shared\"\n                ) else {\n                    print(\"Failed to get/init shared defaults\")\n                    return\n                }\n\n                defaults.set(newValue, forKey: \"enableOpenInCE\")\n            }\n    }\n\n    var revealFileOnFocusChangeToggle: some View {\n        Toggle(\"Automatically reveal in project navigator\", isOn: $settings.revealFileOnFocusChange)\n    }\n\n    private static let formatter = configure(DateFormatter()) {\n        $0.dateStyle = .medium\n        $0.timeStyle = .medium\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/GeneralSettings/Models/GeneralSettings.swift",
    "content": "//\n//  GeneralSettings.swift\n//  CodeEditModules/Settings\n//\n//  Created by Nanashi Li on 2022/04/08.\n//\n\nimport SwiftUI\n\nextension SettingsData {\n\n    /// The general global setting\n    struct GeneralSettings: Codable, Hashable, SearchableSettingsPage {\n\n        /// The appearance of the app\n        var appAppearance: Appearances = .system\n\n        /// The show issues behavior of the app\n        var showIssues: Issues = .inline\n\n        /// The show live issues behavior of the app\n        var showLiveIssues: Bool = true\n\n        /// The search keys\n        var searchKeys: [String] {\n            [\n                \"Appearance\",\n                \"File Icon Style\",\n                \"Tab Bar Style\",\n                \"Show Jump Bar\",\n                \"Dim editors without focus\",\n                \"Navigator Tab Bar Position\",\n                \"Inspector Tab Bar Position\",\n                \"Show Issues\",\n                \"Show Live Issues\",\n                \"Automatically save change to disk\",\n                \"Automatically reveal in project navigator\",\n                \"Reopen Behavior\",\n                \"After the last window is closed\",\n                \"File Extensions\",\n                \"Project Navigator Size\",\n                \"Find Navigator Detail\",\n                \"Issue Navigator Detail\",\n                \"Show “Open With CodeEdit“ option in Finder\",\n                \"'codeedit' Shell command\",\n                \"Dialog Warnings\",\n                \"Check for updates\",\n                \"Automatically check for app updates\",\n                \"Include pre-release versions\"\n            ]\n            .map { NSLocalizedString($0, comment: \"\") }\n        }\n\n        /// Show editor jump bar\n        var showEditorJumpBar: Bool = true\n\n        /// Dims editors without focus\n        var dimEditorsWithoutFocus: Bool = false\n\n        /// The show file extensions behavior of the app\n        var fileExtensionsVisibility: FileExtensionsVisibility = .showAll\n\n        /// The file extensions collection to display\n        var shownFileExtensions: FileExtensions = .default\n\n        /// The file extensions collection to hide\n        var hiddenFileExtensions: FileExtensions = .default\n\n        /// The style for file icons\n        var fileIconStyle: FileIconStyle = .color\n\n        /// The position for the navigator sidebar tab bar\n        var navigatorTabBarPosition: SidebarTabBarPosition = .top\n\n        /// The position for the inspector sidebar tab bar\n        var inspectorTabBarPosition: SidebarTabBarPosition = .top\n\n        /// The reopen behavior of the app\n        var reopenBehavior: ReopenBehavior = .welcome\n\n        /// Decides what the app does after a workspace is closed\n        var reopenWindowAfterClose: ReopenWindowBehavior = .doNothing\n\n        /// The size of the project navigator\n        var projectNavigatorSize: ProjectNavigatorSize = .medium\n\n        /// The Find Navigator Detail line limit\n        var findNavigatorDetail: NavigatorDetail = .upTo3\n\n        /// The Issue Navigator Detail line limit\n        var issueNavigatorDetail: NavigatorDetail = .upTo3\n\n        /// The reveal file in navigator when focus changes behavior of the app.\n        var revealFileOnFocusChange: Bool = false\n\n        /// Auto save behavior toggle\n        var isAutoSaveOn: Bool = true\n\n        /// Default initializer\n        init() {}\n\n        // swiftlint:disable function_body_length\n        /// Explicit decoder init for setting default values when key is not present in `JSON`\n        init(from decoder: Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.appAppearance = try container.decodeIfPresent(\n                Appearances.self,\n                forKey: .appAppearance\n            ) ?? .system\n            self.showIssues = try container.decodeIfPresent(\n                Issues.self,\n                forKey: .showIssues\n            ) ?? .inline\n            self.showLiveIssues = try container.decodeIfPresent(\n                Bool.self,\n                forKey: .showLiveIssues\n            ) ?? true\n            self.showEditorJumpBar = try container.decodeIfPresent(\n                Bool.self,\n                forKey: .showEditorJumpBar\n            ) ?? true\n            self.dimEditorsWithoutFocus = try container.decodeIfPresent(\n                Bool.self,\n                forKey: .dimEditorsWithoutFocus\n            ) ?? false\n            self.fileExtensionsVisibility = try container.decodeIfPresent(\n                FileExtensionsVisibility.self,\n                forKey: .fileExtensionsVisibility\n            ) ?? .showAll\n            self.shownFileExtensions = try container.decodeIfPresent(\n                FileExtensions.self,\n                forKey: .shownFileExtensions\n            ) ?? .default\n            self.hiddenFileExtensions = try container.decodeIfPresent(\n                FileExtensions.self,\n                forKey: .hiddenFileExtensions\n            ) ?? .default\n            self.fileIconStyle = try container.decodeIfPresent(\n                FileIconStyle.self,\n                forKey: .fileIconStyle\n            ) ?? .color\n            self.navigatorTabBarPosition = try container.decodeIfPresent(\n                SidebarTabBarPosition.self,\n                forKey: .navigatorTabBarPosition\n            ) ?? .top\n            self.inspectorTabBarPosition = try container.decodeIfPresent(\n                SidebarTabBarPosition.self,\n                forKey: .inspectorTabBarPosition\n            ) ?? .top\n            self.reopenBehavior = try container.decodeIfPresent(\n                ReopenBehavior.self,\n                forKey: .reopenBehavior\n            ) ?? .welcome\n            self.reopenWindowAfterClose = try container.decodeIfPresent(\n                ReopenWindowBehavior.self,\n                forKey: .reopenWindowAfterClose\n            ) ?? .doNothing\n            self.projectNavigatorSize = try container.decodeIfPresent(\n                ProjectNavigatorSize.self,\n                forKey: .projectNavigatorSize\n            ) ?? .medium\n            self.findNavigatorDetail = try container.decodeIfPresent(\n                NavigatorDetail.self,\n                forKey: .findNavigatorDetail\n            ) ?? .upTo3\n            self.issueNavigatorDetail = try container.decodeIfPresent(\n                NavigatorDetail.self,\n                forKey: .issueNavigatorDetail\n            ) ?? .upTo3\n            self.revealFileOnFocusChange = try container.decodeIfPresent(\n                Bool.self,\n                forKey: .revealFileOnFocusChange\n            ) ?? false\n            self.isAutoSaveOn = try container.decodeIfPresent(\n                Bool.self,\n                forKey: .isAutoSaveOn\n            ) ?? true\n        }\n        // swiftlint:enable function_body_length\n    }\n\n    /// The appearance of the app\n    /// - **system**: uses the system appearance\n    /// - **dark**: always uses dark appearance\n    /// - **light**: always uses light appearance\n    enum Appearances: String, Codable {\n        case system\n        case light\n        case dark\n\n        /// Applies the selected appearance\n        func applyAppearance() {\n            switch self {\n            case .system:\n                NSApp.appearance = nil\n\n            case .dark:\n                NSApp.appearance = .init(named: .darkAqua)\n\n            case .light:\n                NSApp.appearance = .init(named: .aqua)\n            }\n        }\n    }\n\n    /// The style for issues display\n    ///  - **inline**: Issues show inline\n    ///  - **minimized** Issues show minimized\n    enum Issues: String, Codable {\n        case inline\n        case minimized\n    }\n\n    /// The style for file extensions visibility\n    ///  - **hideAll**: File extensions are hidden\n    ///  - **showAll** File extensions are visible\n    ///  - **showOnly** Specific file extensions are visible\n    ///  - **hideOnly** Specific file extensions are hidden\n    enum FileExtensionsVisibility: Codable, Hashable {\n        case hideAll\n        case showAll\n        case showOnly\n        case hideOnly\n    }\n\n    /// The collection of file extensions used by\n    /// ``FileExtensionsVisibility/showOnly`` or  ``FileExtensionsVisibility/hideOnly`` preference\n    struct FileExtensions: Codable, Hashable {\n        var extensions: [String]\n\n        var string: String {\n            get {\n                extensions.joined(separator: \", \")\n            }\n            set {\n                extensions = newValue\n                    .components(separatedBy: \",\")\n                    .map({ $0.trimmingCharacters(in: .whitespacesAndNewlines) })\n                    .filter({ !$0.isEmpty || string.count < newValue.count })\n            }\n        }\n\n        static var `default` = FileExtensions(extensions: [\n            \"c\", \"cc\", \"cpp\", \"h\", \"hpp\", \"m\", \"mm\", \"gif\",\n            \"icns\", \"jpeg\", \"jpg\", \"png\", \"tiff\", \"swift\"\n        ])\n    }\n    /// The style for file icons\n    /// - **color**: File icons appear in their default colors\n    /// - **monochrome**: File icons appear monochromatic\n    enum FileIconStyle: String, Codable {\n        case color\n        case monochrome\n    }\n\n    /// The position for a sidebar tab bar\n    /// - **top**: Tab bar is positioned at the top of the sidebar\n    /// - **side**: Tab bar is positioned to the side of the sidebar\n    enum SidebarTabBarPosition: String, Codable {\n        case top, side\n    }\n\n    /// The reopen behavior of the app\n    /// - **welcome**: On restart the app will show the welcome screen\n    /// - **openPanel**: On restart the app will show an open panel\n    /// - **newDocument**: On restart a new empty document will be created\n    enum ReopenBehavior: String, Codable {\n        case welcome\n        case openPanel\n        case newDocument\n    }\n\n    enum ReopenWindowBehavior: String, Codable {\n        case showWelcomeWindow\n        case doNothing\n        case quit\n    }\n\n    enum ProjectNavigatorSize: String, Codable {\n        case small\n        case medium\n        case large\n\n        /// Returns the row height depending on the `projectNavigatorSize` in `Settings`.\n        ///\n        /// * `small`: 20\n        /// * `medium`: 22\n        /// * `large`: 24\n        var rowHeight: Double {\n            switch self {\n            case .small: return 20\n            case .medium: return 22\n            case .large: return 24\n            }\n        }\n    }\n\n    /// The Navigation Detail behavior of the app\n    ///  - Use **rawValue** to set lineLimit\n    enum NavigatorDetail: Int, Codable, CaseIterable {\n        case upTo1 = 1\n        case upTo2 = 2\n        case upTo3 = 3\n        case upTo4 = 4\n        case upTo5 = 5\n        case upTo10 = 10\n        case upTo30 = 30\n\n        var label: String {\n            switch self {\n            case .upTo1:\n                return \"One Line\"\n            default:\n                return \"Up to \\(self.rawValue) lines\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/GeneralSettings/View+actionBar.swift",
    "content": "//\n//  View+actionBar.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 9/18/23.\n//\n\nimport SwiftUI\n\nextension View {\n    func actionBar<Content: View>(@ViewBuilder content: () -> Content) -> some View {\n        self\n            .padding(.bottom, 24)\n            .overlay(alignment: .bottom) {\n                VStack(spacing: -1) {\n                    Divider()\n                    HStack(spacing: 0) {\n                        content()\n                            .buttonStyle(.icon(font: Font.system(size: 11, weight: .medium), size: 24))\n                    }\n                    .frame(height: 16)\n                    .padding(.vertical, 4)\n                    .frame(maxWidth: .infinity, alignment: .leading)\n                }\n                .frame(height: 24)\n                .background(.separator)\n            }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/Keybindings/Models/KeybindingsSettings.swift",
    "content": "//\n//  KeybindingsPreferences.swift\n//  CodeEditModules/Settings\n//  \n//  Created by Alex on 18.05.2022.\n//\n\nimport Foundation\n\nextension SettingsData {\n\n    /// The global settings for text editing\n    struct KeybindingsSettings: Codable, Hashable {\n\n        /// An integer indicating how many spaces a `tab` will generate\n        var keybindings: [String: KeyboardShortcutWrapper] = .init()\n\n        /// Default initializer\n        init() {\n            self.keybindings = KeybindingManager.shared.keyboardShortcuts\n        }\n\n        /// Explicit decoder init for setting default values when key is not present in `JSON`\n        init(from decoder: Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.keybindings = try container.decodeIfPresent(\n                [String: KeyboardShortcutWrapper].self,\n                forKey: .keybindings\n            ) ?? .init()\n            appendNew()\n        }\n\n        /// Adds new keybindings if they were added to default_keybindings.json.\n        /// To ensure users will get new keybindings with new app version releases\n        private mutating func appendNew() {\n            let newKeybindings = KeybindingManager.shared\n                .keyboardShortcuts.filter { !keybindings.keys.contains($0.key) }\n            for keybinding in newKeybindings {\n                self.keybindings[keybinding.key] = KeybindingManager.shared.named(with: keybinding.key)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/LocationsSettings/LocationsSettingsView.swift",
    "content": "//\n//  LocationSettingsView.swift\n//  CodeEdit\n//\n//  Created by Raymond Vleeshouwer on 02/04/23.\n//\n\nimport SwiftUI\n\n/// A view that implements the `Locations` settings section\nstruct LocationsSettingsView: View {\n    var body: some View {\n        SettingsForm {\n            Section {\n                applicationSupportLocation\n                settingsLocation\n                themesLocation\n                extensionsLocation\n            }\n        }\n    }\n}\n\nprivate extension LocationsSettingsView {\n    @ViewBuilder private var applicationSupportLocation: some View {\n        ExternalLink(destination: Settings.shared.baseURL) {\n            Text(\"Application Support\")\n            Text(Settings.shared.baseURL.path)\n                .font(.footnote)\n                .foregroundColor(.secondary)\n        }\n    }\n\n    private var settingsLocation: some View {\n        ExternalLink(destination: ThemeModel.shared.settingsURL) {\n            Text(\"Settings\")\n            Text(ThemeModel.shared.settingsURL.path)\n                .font(.footnote)\n                .foregroundColor(.secondary)\n        }\n    }\n\n    private var themesLocation: some View {\n        ExternalLink(destination: ThemeModel.shared.themesURL) {\n            Text(\"Themes\")\n            Text(ThemeModel.shared.themesURL.path)\n                .font(.footnote)\n                .foregroundColor(.secondary)\n        }\n    }\n\n    private var extensionsLocation: some View {\n        ExternalLink(destination: ThemeModel.shared.extensionsURL) {\n            Text(\"Extensions\")\n            Text(ThemeModel.shared.extensionsURL.path())\n                .font(.footnote)\n                .foregroundColor(.secondary)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/LocationsSettings/Models/LocationsSettings.swift",
    "content": "//\n//  LocationsSettings.swift\n//  CodeEdit\n//\n//  Created by Raymond Vleeshouwer on 24/06/23.\n//\n\nimport Foundation\n\nextension SettingsData {\n\n    struct LocationsSettings: SearchableSettingsPage {\n\n        /// The search keys\n        var searchKeys: [String] {\n            [\n                \"Settings Location\",\n                \"Themes Location\",\n                \"Extensions Location\"\n            ]\n            .map { NSLocalizedString($0, comment: \"\") }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/NavigationSettings/Models/NavigationSettings.swift",
    "content": "//\n//  NavigationSettings.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 3/4/24.\n//\n\nimport Foundation\n\nextension SettingsData {\n\n    /// The global settings for the terminal emulator\n    struct NavigationSettings: Codable, Hashable, SearchableSettingsPage {\n\n        /// The search keys\n        var searchKeys: [String] {\n            [\n                \"Navigation Style\",\n            ]\n            .map { NSLocalizedString($0, comment: \"\") }\n        }\n\n        /// Navigation style used\n        var navigationStyle: NavigationStyle = .openInTabs\n\n        /// Default initializer\n        init() {}\n\n        /// Explicit decoder init for setting default values when key is not present in `JSON`\n        init(from decoder: Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.navigationStyle = try container.decodeIfPresent(\n                NavigationStyle.self, forKey: .navigationStyle\n            ) ?? .openInTabs\n        }\n    }\n\n    enum NavigationStyle: String, Codable, Hashable {\n        case openInTabs\n        case openInPlace\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/NavigationSettings/NavigationSettingsView.swift",
    "content": "//\n//  NavigationSettingsView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 3/4/24.\n//\n\nimport SwiftUI\n\nstruct NavigationSettingsView: View {\n    @AppSettings(\\.navigation)\n    var settings\n\n    var body: some View {\n        SettingsForm {\n            Section {\n                navigationStyle\n            }\n        }\n    }\n}\n\nprivate extension NavigationSettingsView {\n    private var navigationStyle: some View {\n        Picker(\"Navigation Style\", selection: $settings.navigationStyle) {\n            Text(\"Open in Tabs\")\n                .tag(SettingsData.NavigationStyle.openInTabs)\n            Text(\"Open in Place\")\n                .tag(SettingsData.NavigationStyle.openInPlace)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/SearchSettings/Models/SearchSettings.swift",
    "content": "//\n//  SearchSettings.swift\n//  CodeEdit\n//\n//  Created by Esteban on 12/10/23.\n//\n\nimport Foundation\n\nextension SettingsData {\n    struct SearchSettings: Codable, Hashable, SearchableSettingsPage {\n\n        /// The search keys\n        var searchKeys: [String] {\n            [\n                \"Ignore Glob Patterns\",\n                \"Ignore Patterns\"\n            ]\n            .map { NSLocalizedString($0, comment: \"\") }\n        }\n\n        /// List of Glob Patterns that determine which files or directories to ignore\n        var ignoreGlobPatterns: [GlobPattern] = .init()\n\n        /// Default initializer\n        init() {}\n\n        /// Explicit decoder init for setting default values when key is not present in `JSON`\n        init(from decoder: Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n\n            self.ignoreGlobPatterns = try container.decodeIfPresent(\n                [GlobPattern].self,\n                forKey: .ignoreGlobPatterns\n            ) ?? []\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/SearchSettings/Models/SearchSettingsModel.swift",
    "content": "//\n//  SearchSettingsModel.swift\n//  CodeEdit\n//\n//  Created by Esteban on 12/10/23.\n//\n\nimport SwiftUI\n\n/// The Search Settings View Model. Accessible via the singleton \"``SearchSettings/shared``\".\n///\n/// **Usage:**\n/// ```swift\n/// @StateObject\n/// private var searchSettigs: SearchSettingsModel = .shared\n/// ```\nfinal class SearchSettingsModel: ObservableObject {\n    /// Reads settings file for Search Settings and updates the values in this model\n    /// correspondingly\n    private init() {\n        let value = Settings[\\.search].ignoreGlobPatterns\n        self.ignoreGlobPatterns = value\n    }\n\n    static let shared: SearchSettingsModel = .init()\n\n    /// Default instance of the `FileManager`\n    private let filemanager = FileManager.default\n\n    /// The base folder url `~/Library/Application Support/CodeEdit/`\n    private var baseURL: URL {\n        filemanager.homeDirectoryForCurrentUser.appending(path: \"Library/Application Support/CodeEdit\")\n    }\n\n    /// The URL of the `search` folder\n    internal var searchURL: URL {\n        baseURL.appending(path: \"search\", directoryHint: .isDirectory)\n    }\n\n    /// The URL of the `Extensions` folder\n    internal var extensionsURL: URL {\n        baseURL.appending(path: \"Extensions\", directoryHint: .isDirectory)\n    }\n\n    /// The URL of the `settings.json` file\n    internal var settingsURL: URL {\n        baseURL.appending(path: \"settings.json\", directoryHint: .isDirectory)\n    }\n\n    /// Selected patterns\n    @Published var selection: Set<UUID> = []\n\n    /// Stores the new values from the Search Settings Model into the settings.json whenever\n    /// `ignoreGlobPatterns` is updated\n    @Published var ignoreGlobPatterns: [GlobPattern] {\n        didSet {\n            DispatchQueue.main.async {\n                Settings[\\.search].ignoreGlobPatterns = self.ignoreGlobPatterns\n            }\n        }\n    }\n\n    func getPattern(for id: UUID) -> GlobPattern? {\n        return ignoreGlobPatterns.first(where: { $0.id == id })\n    }\n\n    func addPattern() {\n        ignoreGlobPatterns.append(GlobPattern(value: \"\"))\n    }\n\n    func removePatterns(_ selection: Set<UUID>? = nil) {\n        let patternsToRemove = selection?.compactMap { getPattern(for: $0) } ?? []\n        ignoreGlobPatterns.removeAll { patternsToRemove.contains($0) }\n        self.selection.removeAll()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/SearchSettings/SearchSettingsIgnoreGlobPatternItemView.swift",
    "content": "//\n//  SearchSettingsIgnoreGlobPatternItemView.swift\n//  CodeEdit\n//\n//  Created by Esteban on 12/10/23.\n//\n\nimport SwiftUI\n\nstruct SearchSettingsIgnoreGlobPatternItemView: View {\n    @Binding var globPattern: String\n\n    var body: some View {\n        Text(globPattern)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/SearchSettings/SearchSettingsView.swift",
    "content": "//\n//  SearchSettingsView.swift\n//  CodeEdit\n//\n//  Created by Esteban on 12/10/23.\n//\n\nimport SwiftUI\n\nstruct SearchSettingsView: View {\n    var body: some View {\n        SettingsForm {\n            Section {\n                ExcludedGlobPatternList()\n            } header: {\n                Text(\"Exclude\")\n                Text(\n                    \"Add glob patterns to exclude matching files and folders from searches and open quickly. \" +\n                    \"This will inherit glob patterns from the Exclude from Project setting.\"\n                )\n            }\n        }\n    }\n}\n\nstruct ExcludedGlobPatternList: View {\n    @ObservedObject private var model: SearchSettingsModel = .shared\n\n    var body: some View {\n        GlobPatternList(\n            patterns: $model.ignoreGlobPatterns,\n            selection: $model.selection,\n            addPattern: model.addPattern,\n            removePatterns: model.removePatterns,\n            emptyMessage: \"No excluded glob patterns\"\n        )\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/SourceControlSettings/IgnoredFilesListView.swift",
    "content": "//\n//  IgnoredFilesListView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/1/24.\n//\n\nimport SwiftUI\n\nstruct IgnoredFilesListView: View {\n    @StateObject private var model = IgnorePatternModel()\n\n    var body: some View {\n        GlobPatternList(\n            patterns: $model.patterns,\n            selection: $model.selection,\n            addPattern: model.addPattern,\n            removePatterns: model.removePatterns,\n            emptyMessage: \"No ignored files\"\n        )\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/SourceControlSettings/Models/IgnorePatternModel.swift",
    "content": "//\n//  IgnorePatternModel.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/1/24.\n//\n\nimport Foundation\n\n/// A model to manage Git ignore patterns for a file, including loading, saving, and monitoring changes.\n@MainActor\nclass IgnorePatternModel: ObservableObject {\n    /// Indicates whether patterns are currently being loaded from the Git ignore file.\n    @Published var loadingPatterns: Bool = false\n\n    /// A collection of Git ignore patterns being managed by this model.\n    @Published var patterns: [GlobPattern] = [] {\n        didSet {\n            if !loadingPatterns {\n                savePatterns()\n            } else {\n                loadingPatterns = false\n            }\n        }\n    }\n\n    /// Tracks the selected patterns by their unique identifiers (UUIDs).\n    @Published var selection: Set<UUID> = []\n\n    /// A client for interacting with the Git configuration.\n    private let gitConfig = GitConfigClient(shellClient: currentWorld.shellClient)\n\n    /// A file system monitor for detecting changes to the Git ignore file.\n    private var fileMonitor: DispatchSourceFileSystemObject?\n\n    /// Task tracking the current save operation\n    private var savingTask: Task<Void, Never>?\n\n    init() {\n        Task {\n            try? await startFileMonitor()\n            await loadPatterns()\n        }\n    }\n\n    deinit {\n        Task { @MainActor [weak self] in\n            self?.stopFileMonitor()\n        }\n    }\n\n    /// Resolves the URL for the Git ignore file.\n    /// - Returns: The resolved `URL` for the Git ignore file.\n    private func gitIgnoreURL() async throws -> URL {\n        let excludesFile = try await gitConfig.get(key: \"core.excludesfile\") ?? \"\"\n        if !excludesFile.isEmpty {\n            if excludesFile.starts(with: \"~/\") {\n                let relativePath = String(excludesFile.dropFirst(2)) // Remove \"~/\"\n                return FileManager.default.homeDirectoryForCurrentUser.appending(path: relativePath)\n            } else if excludesFile.starts(with: \"/\") {\n                return URL(fileURLWithPath: excludesFile) // Absolute path\n            } else {\n                return FileManager.default.homeDirectoryForCurrentUser.appending(path: excludesFile)\n            }\n        } else {\n            let defaultPath = \".gitignore_global\"\n            let fileURL = FileManager.default.homeDirectoryForCurrentUser.appending(path: defaultPath)\n            await gitConfig.set(key: \"core.excludesfile\", value: \"~/\\(defaultPath)\", global: true)\n            return fileURL\n        }\n    }\n\n    /// Starts monitoring the Git ignore file for changes.\n    private func startFileMonitor() async throws {\n        let fileURL = try await gitIgnoreURL()\n        let fileDescriptor = open(fileURL.path, O_EVTONLY)\n        guard fileDescriptor != -1 else { return }\n\n        let source = DispatchSource.makeFileSystemObjectSource(\n            fileDescriptor: fileDescriptor,\n            eventMask: .write,\n            queue: DispatchQueue.main\n        )\n\n        source.setEventHandler {\n            Task { await self.loadPatterns() }\n        }\n\n        source.setCancelHandler {\n            close(fileDescriptor)\n        }\n\n        fileMonitor?.cancel()\n        fileMonitor = source\n        source.resume()\n    }\n\n    /// Stops monitoring the Git ignore file.\n    private func stopFileMonitor() {\n        fileMonitor?.cancel()\n        fileMonitor = nil\n    }\n\n    /// Loads patterns from the Git ignore file into the `patterns` property.\n    func loadPatterns() async {\n        loadingPatterns = true\n\n        do {\n            let fileURL = try await gitIgnoreURL()\n            guard FileManager.default.fileExists(atPath: fileURL.path) else {\n                patterns = []\n                loadingPatterns = false\n                return\n            }\n\n            if let content = try? String(contentsOf: fileURL) {\n                patterns = content.split(separator: \"\\n\")\n                    .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }\n                    .filter { !$0.isEmpty && !$0.starts(with: \"#\") }\n                    .map { GlobPattern(value: String($0)) }\n                loadingPatterns = false\n            } else {\n                patterns = []\n                loadingPatterns = false\n            }\n        } catch {\n            print(\"Error loading patterns: \\(error)\")\n            patterns = []\n            loadingPatterns = false\n        }\n    }\n\n    /// Retrieves the pattern associated with a specific UUID.\n    /// - Parameter id: The UUID of the pattern to retrieve.\n    /// - Returns: The matching `GlobPattern`, if found.\n    func getPattern(for id: UUID) -> GlobPattern? {\n        return patterns.first(where: { $0.id == id })\n    }\n\n    /// Saves the current patterns back to the Git ignore file.\n    @MainActor\n    func savePatterns() {\n        // Cancel the existing task if it exists\n        savingTask?.cancel()\n\n        // Start a new task for saving patterns\n        savingTask = Task {\n            stopFileMonitor()\n            defer {\n                savingTask = nil // Clear the task when done\n                Task { try? await startFileMonitor() }\n            }\n\n            do {\n                let fileURL = try await gitIgnoreURL()\n                guard let fileContent = try? String(contentsOf: fileURL) else {\n                    await writeAllPatterns()\n                    return\n                }\n\n                let lines = fileContent.split(separator: \"\\n\", omittingEmptySubsequences: false).map(String.init)\n                let (patternToLineIndex, nonPatternLines) = mapLines(lines)\n                let globalCommentLines = extractGlobalComments(nonPatternLines, patternToLineIndex)\n\n                var reorderedLines = reorderPatterns(globalCommentLines, patternToLineIndex, nonPatternLines, lines)\n\n                // Ensure single blank line at the end\n                reorderedLines = cleanUpWhitespace(in: reorderedLines)\n\n                // Write the updated content back to the file\n                let updatedContent = reorderedLines.joined(separator: \"\\n\")\n                try updatedContent.write(to: fileURL, atomically: true, encoding: .utf8)\n            } catch {\n                print(\"Error saving patterns: \\(error)\")\n            }\n        }\n    }\n\n    /// Maps lines to patterns and non-pattern lines (e.g., comments or whitespace).\n    private func mapLines(_ lines: [String]) -> ([String: Int], [(line: String, index: Int)]) {\n        var patternToLineIndex: [String: Int] = [:]\n        var nonPatternLines: [(line: String, index: Int)] = []\n\n        for (index, line) in lines.enumerated() {\n            let trimmedLine = line.trimmingCharacters(in: .whitespaces)\n            if !trimmedLine.isEmpty && !trimmedLine.hasPrefix(\"#\") {\n                patternToLineIndex[trimmedLine] = index\n            } else if index != lines.count - 1 {\n                nonPatternLines.append((line: line, index: index))\n            }\n        }\n\n        return (patternToLineIndex, nonPatternLines)\n    }\n\n    /// Extracts global comments from the non-pattern lines.\n    private func extractGlobalComments(\n        _ nonPatternLines: [(line: String, index: Int)],\n        _ patternToLineIndex: [String: Int]\n    ) -> [String] {\n        let globalComments = nonPatternLines.filter { $0.index < (patternToLineIndex.values.min() ?? Int.max) }\n        return globalComments.map(\\.line)\n    }\n\n    /// Reorders patterns while preserving associated comments and whitespace.\n    private func reorderPatterns(\n        _ globalCommentLines: [String],\n        _ patternToLineIndex: [String: Int],\n        _ nonPatternLines: [(line: String, index: Int)],\n        _ lines: [String]\n    ) -> [String] {\n        var reorderedLines: [String] = globalCommentLines\n        var usedNonPatternLines = Set<Int>()\n        var usedPatterns = Set<String>()\n\n        for pattern in patterns {\n            let value = pattern.value\n\n            // Insert the pattern\n            reorderedLines.append(value)\n            usedPatterns.insert(value)\n\n            // Preserve associated non-pattern lines\n            if let currentIndex = patternToLineIndex[value] {\n                for nextIndex in (currentIndex + 1)..<lines.count {\n                    if let nonPatternLine = nonPatternLines.first(where: { $0.index == nextIndex }),\n                       !usedNonPatternLines.contains(nonPatternLine.index) {\n                        reorderedLines.append(nonPatternLine.line)\n                        usedNonPatternLines.insert(nonPatternLine.index)\n                    } else {\n                        break\n                    }\n                }\n            }\n        }\n\n        // Retain non-pattern lines that follow deleted patterns\n        for (line, index) in nonPatternLines {\n            if !usedNonPatternLines.contains(index) && !reorderedLines.contains(line) {\n                reorderedLines.append(line)\n                usedNonPatternLines.insert(index)\n            }\n        }\n\n        // Add new patterns that were not in the original file\n        for pattern in patterns where !usedPatterns.contains(pattern.value) {\n            reorderedLines.append(pattern.value)\n        }\n\n        return reorderedLines\n    }\n\n    /// Writes all patterns to the Git ignore file.\n    private func writeAllPatterns() async {\n        do {\n            let fileURL = try await gitIgnoreURL()\n            if !FileManager.default.fileExists(atPath: fileURL.path) {\n                FileManager.default.createFile(atPath: fileURL.path, contents: nil)\n            }\n\n            let content = patterns.map(\\.value).joined(separator: \"\\n\")\n            try content.write(to: fileURL, atomically: true, encoding: .utf8)\n        } catch {\n            print(\"Failed to write all patterns: \\(error)\")\n        }\n    }\n\n    /// Cleans up extra whitespace from lines.\n    private func cleanUpWhitespace(in lines: [String]) -> [String] {\n        var cleanedLines: [String] = []\n        var previousLineWasBlank = false\n\n        for line in lines {\n            let isBlank = line.trimmingCharacters(in: .whitespaces).isEmpty\n            if !(isBlank && previousLineWasBlank) {\n                cleanedLines.append(line)\n            }\n            previousLineWasBlank = isBlank\n        }\n\n        // Trim extra blank lines at the end, ensuring only a single blank line\n        while let lastLine = cleanedLines.last, lastLine.trimmingCharacters(in: .whitespaces).isEmpty {\n            cleanedLines.removeLast()\n        }\n        cleanedLines.append(\"\") // Ensure exactly one blank line at the end\n\n        // Trim whitespace at the top of the file\n        while let firstLine = cleanedLines.first, firstLine.trimmingCharacters(in: .whitespaces).isEmpty {\n            cleanedLines.removeFirst()\n        }\n\n        return cleanedLines\n    }\n\n    /// Adds a new, empty pattern to the list of patterns.\n    func addPattern() {\n        patterns.append(GlobPattern(value: \"\"))\n    }\n\n    /// Removes the specified patterns from the list of patterns.\n    /// - Parameter selection: The set of UUIDs for the patterns to remove. If `nil`, no patterns are removed.\n    func removePatterns(_ selection: Set<UUID>? = nil) {\n        let patternsToRemove = selection?.compactMap { getPattern(for: $0) } ?? []\n        patterns.removeAll { patternsToRemove.contains($0) }\n        self.selection.removeAll()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/SourceControlSettings/Models/SourceControlSettings.swift",
    "content": "//\n//  SourceControlPreferences.swift\n//  CodeEditModules/Settings\n//\n//  Created by Nanashi Li on 2022/04/08.\n//\n\nimport Foundation\n\nextension SettingsData {\n    /// The global settings for source control\n    struct SourceControlSettings: Codable, Hashable, SearchableSettingsPage {\n\n        var searchKeys: [String] {\n            [\n                \"General\",\n                \"Enable source control\",\n                \"Refresh local status automatically\",\n                \"Fetch and refresh server status automatically\",\n                \"Add and remove files automatically\",\n                \"Select files to commit automatically\",\n                \"Show source control changes\",\n                \"Include upstream changes\",\n                \"Comparison view\",\n                \"Source control navigator\",\n                \"Default branch name\",\n                \"Git\",\n                \"Author Name\",\n                \"Author Email\",\n                \"Prefer to rebase when pulling\",\n                \"Show merge commits in per-file log\"\n            ]\n            .map { NSLocalizedString($0, comment: \"\") }\n        }\n\n        /// The general source control settings\n        var general: SourceControlGeneral = .init()\n\n        /// The source control git settings\n        var git: SourceControlGit = .init()\n\n        /// Default initializer\n        init() {}\n\n        /// Explicit decoder init for setting default values when key is not present in `JSON`\n        init(from decoder: Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.general = try container.decodeIfPresent(SourceControlGeneral.self, forKey: .general) ?? .init()\n            self.git = try container.decodeIfPresent(SourceControlGit.self, forKey: .git) ?? .init()\n        }\n    }\n\n    struct SourceControlGeneral: Codable, Hashable {\n        /// Indicates whether or not the source control is active\n        var sourceControlIsEnabled: Bool = true\n        /// Indicates whether the status should be refreshed locally without fetching updates from the server.\n        var refreshStatusLocally: Bool = true\n        /// Indicates whether the application should automatically fetch updates from the server and refresh the status.\n        var fetchRefreshServerStatus: Bool = true\n        /// Indicates whether new and deleted files should be automatically staged for commit.\n        var addRemoveAutomatically: Bool = true\n        /// Indicates whether the application should automatically select files to commit.\n        var selectFilesToCommit: Bool = true\n        /// Indicates whether or not to show the source control changes\n        var showSourceControlChanges: Bool = true\n        /// Indicates whether or not we should include the upstream\n        var includeUpstreamChanges: Bool = true\n        /// Indicates whether or not we should open the reported feedback in the browser\n        var openFeedbackInBrowser: Bool = true\n        /// The selected value of the comparison view\n        var revisionComparisonLayout: RevisionComparisonLayout = .localLeft\n        /// The selected value of the control navigator\n        var controlNavigatorOrder: ControlNavigatorOrder = .sortByName\n        /// Default initializer\n        init() {}\n        /// Explicit decoder init for setting default values when key is not present in `JSON`\n        init(from decoder: Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.sourceControlIsEnabled = try container.decodeIfPresent(\n                Bool.self,\n                forKey: .sourceControlIsEnabled\n            ) ?? true\n            self.refreshStatusLocally = try container.decodeIfPresent(Bool.self, forKey: .refreshStatusLocally) ?? true\n            self.fetchRefreshServerStatus = try container.decodeIfPresent(\n                Bool.self,\n                forKey: .fetchRefreshServerStatus\n            ) ?? true\n            self.addRemoveAutomatically = try container.decodeIfPresent(\n                Bool.self,\n                forKey: .addRemoveAutomatically\n            ) ?? true\n            self.selectFilesToCommit = try container.decodeIfPresent(Bool.self, forKey: .selectFilesToCommit) ?? true\n            self.showSourceControlChanges = try container.decodeIfPresent(\n                Bool.self,\n                forKey: .showSourceControlChanges\n            ) ?? true\n            self.includeUpstreamChanges = try container.decodeIfPresent(\n                Bool.self,\n                forKey: .includeUpstreamChanges\n            ) ?? true\n            self.openFeedbackInBrowser = try container.decodeIfPresent(\n                Bool.self,\n                forKey: .openFeedbackInBrowser\n            ) ?? true\n            self.revisionComparisonLayout = try container.decodeIfPresent(\n                RevisionComparisonLayout.self,\n                forKey: .revisionComparisonLayout\n            ) ?? .localLeft\n            self.controlNavigatorOrder = try container.decodeIfPresent(\n                ControlNavigatorOrder.self,\n                forKey: .controlNavigatorOrder\n            ) ?? .sortByName\n        }\n    }\n\n    /// The style for comparison View\n    /// - **localLeft**: Local Revision on Left Side\n    /// - **localRight**: Local Revision on Right Side\n    enum RevisionComparisonLayout: String, Codable {\n        case localLeft\n        case localRight\n    }\n\n    /// The style for control Navigator\n    /// - **sortName**: They are sorted by Name\n    /// - **sortDate**: They are sorted by Date\n    enum ControlNavigatorOrder: String, Codable {\n        case sortByName\n        case sortByDate\n    }\n\n    struct SourceControlGit: Codable, Hashable {\n        /// Indicates whether we should rebase when pulling commits\n        var showMergeCommitsPerFileLog: Bool = false\n        /// Default initializer\n        init() {}\n        /// Explicit decoder init for setting default values when key is not present in `JSON`\n        init(from decoder: Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.showMergeCommitsPerFileLog = try container.decodeIfPresent(\n                Bool.self,\n                forKey: .showMergeCommitsPerFileLog\n            ) ?? false\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/SourceControlSettings/SourceControlGeneralView.swift",
    "content": "//\n//  SourceControlGeneralView.swift\n//  CodeEdit\n//\n//  Created by Raymond Vleeshouwer on 02/04/23.\n//\n\nimport SwiftUI\n\nstruct SourceControlGeneralView: View {\n    @AppSettings(\\.sourceControl.general)\n    var settings\n\n    let gitConfig = GitConfigClient(shellClient: currentWorld.shellClient)\n\n    var body: some View {\n        Group {\n            Section(\"Source Control\") {\n                refreshLocalStatusAuto\n                fetchRefreshStatusAuto\n                addRemoveFilesAuto\n                selectFilesToCommitAuto\n            }\n            Section(\"Text Editing\") {\n                showSourceControlChanges\n                includeUpstreamChanges\n            }\n            Section {\n                comparisonView\n                sourceControlNavigator\n            }\n        }\n    }\n}\n\nprivate extension SourceControlGeneralView {\n    private var refreshLocalStatusAuto: some View {\n        Toggle(\n            \"Refresh local status automatically\",\n            isOn: $settings.refreshStatusLocally\n        )\n    }\n\n    private var fetchRefreshStatusAuto: some View {\n        Toggle(\n            \"Fetch and refresh server status automatically\",\n            isOn: $settings.fetchRefreshServerStatus\n        )\n    }\n\n    private var addRemoveFilesAuto: some View {\n        Toggle(\n            \"Add and remove files automatically\",\n            isOn: $settings.addRemoveAutomatically\n        )\n    }\n\n    private var selectFilesToCommitAuto: some View {\n        Toggle(\n            \"Select files to commit automatically\",\n            isOn: $settings.selectFilesToCommit\n        )\n    }\n\n    private var showSourceControlChanges: some View {\n        Toggle(\n            \"Show source control changes\",\n            isOn: $settings.showSourceControlChanges\n        )\n    }\n\n    private var includeUpstreamChanges: some View {\n        Toggle(\n            \"Include upstream changes\",\n            isOn: $settings.includeUpstreamChanges\n        )\n        .disabled(!settings.showSourceControlChanges)\n    }\n\n    private var comparisonView: some View {\n        Picker(\n            \"Comparison view\",\n            selection: $settings.revisionComparisonLayout\n        ) {\n            Text(\"Local Revision on Left Side\")\n                .tag(SettingsData.RevisionComparisonLayout.localLeft)\n            Text(\"Local Revision on Right Side\")\n                .tag(SettingsData.RevisionComparisonLayout.localRight)\n        }\n    }\n\n    private var sourceControlNavigator: some View {\n        Picker(\n            \"Source control navigator\",\n            selection: $settings.controlNavigatorOrder\n        ) {\n            Text(\"Sort by Name\")\n                .tag(SettingsData.ControlNavigatorOrder.sortByName)\n            Text(\"Sort by Date\")\n                .tag(SettingsData.ControlNavigatorOrder.sortByDate)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/SourceControlSettings/SourceControlGitView.swift",
    "content": "//\n//  SourceControlGitView.swift\n//  CodeEdit\n//\n//  Created by Raymond Vleeshouwer on 02/04/23.\n//\n\nimport SwiftUI\n\nstruct SourceControlGitView: View {\n    @AppSettings(\\.sourceControl.git)\n    var git\n\n    let gitConfig = GitConfigClient(shellClient: currentWorld.shellClient)\n\n    @State private var authorName: String = \"\"\n    @State private var authorEmail: String = \"\"\n    @State private var defaultBranch: String = \"\"\n    @State private var preferRebaseWhenPulling: Bool = false\n    @State private var hasAppeared: Bool = false\n    @State private var resolvedGitIgnorePath: String = \"~/.gitignore_global\"\n\n    var body: some View {\n        Group {\n            Section {\n                gitAuthorName\n                gitEmail\n            } header: {\n                Text(\"Git Configuration\")\n                Text(\"\"\"\n                Applied globally to all repositories on your Mac. \\\n                [Learn more...](https://git-scm.com/docs/git-config)\n                \"\"\")\n            }\n            Section {\n                defaultBranchName\n                preferToRebaseWhenPulling\n                showMergeCommitsInPerFileLog\n            }\n            Section {\n                gitConfigEditor\n            }\n            Section {\n                IgnoredFilesListView()\n            } header: {\n                Text(\"Ignored Files\")\n                Text(\"\"\"\n                Patterns for files and folders that Git should ignore and not track. \\\n                Applied globally to all repositories on your Mac. \\\n                [Learn more...](https://git-scm.com/docs/gitignore)\n                \"\"\")\n            }\n            Section {\n                gitIgnoreEditor\n            }\n        }\n        .onAppear {\n            // Intentionally using an onAppear with a Task instead of just a .task modifier.\n            // When we did this it was executing too often.\n            Task {\n                authorName = try await gitConfig.get(key: \"user.name\", global: true) ?? \"\"\n                authorEmail = try await gitConfig.get(key: \"user.email\", global: true) ?? \"\"\n                defaultBranch = try await gitConfig.get(key: \"init.defaultBranch\", global: true) ?? \"\"\n                preferRebaseWhenPulling = try await gitConfig.get(key: \"pull.rebase\", global: true) ?? false\n                try? await Task.sleep(for: .milliseconds(0))\n                hasAppeared = true\n            }\n        }\n    }\n}\n\nprivate extension SourceControlGitView {\n    private var gitAuthorName: some View {\n        TextField(\"Author Name\", text: $authorName)\n            .onChange(of: authorName) { _, newValue in\n                if hasAppeared {\n                    Limiter.debounce(id: \"authorNameDebouncer\", duration: 0.5) {\n                        Task {\n                            await gitConfig.set(key: \"user.name\", value: newValue, global: true)\n                        }\n                    }\n                }\n            }\n    }\n\n    private var gitEmail: some View {\n        TextField(\"Author Email\", text: $authorEmail)\n            .onChange(of: authorEmail) { _, newValue in\n                if hasAppeared {\n                    Limiter.debounce(id: \"authorEmailDebouncer\", duration: 0.5) {\n                        Task {\n                            await gitConfig.set(key: \"user.email\", value: newValue, global: true)\n                        }\n                    }\n                }\n            }\n    }\n\n    private var defaultBranchName: some View {\n        TextField(text: $defaultBranch) {\n            Text(\"Default branch name\")\n            Text(\"Cannot contain spaces, backslashes, or other symbols\")\n        }\n        .onChange(of: defaultBranch) { _, newValue in\n            if hasAppeared {\n                Limiter.debounce(id: \"defaultBranchDebouncer\", duration: 0.5) {\n                    Task {\n                        await gitConfig.set(key: \"init.defaultBranch\", value: newValue, global: true)\n                    }\n                }\n            }\n        }\n    }\n\n    private var preferToRebaseWhenPulling: some View {\n        Toggle(\n            \"Prefer to rebase when pulling\",\n            isOn: $preferRebaseWhenPulling\n        )\n        .onChange(of: preferRebaseWhenPulling) { _, newValue in\n            if hasAppeared {\n                Limiter.debounce(id: \"pullRebaseDebouncer\", duration: 0.5) {\n                    Task {\n                        await gitConfig.set(key: \"pull.rebase\", value: newValue, global: true)\n                    }\n                }\n            }\n        }\n    }\n\n    private var showMergeCommitsInPerFileLog: some View {\n        Toggle(\n            \"Show merge commits in per-file log\",\n            isOn: $git.showMergeCommitsPerFileLog\n        )\n    }\n\n    private var gitConfigEditor: some View {\n        HStack {\n            Text(\"Git configuration is stored in \\\"~/.gitconfig\\\".\")\n                .font(.subheadline)\n                .foregroundStyle(.secondary)\n                .frame(maxWidth: .infinity, alignment: .leading)\n            Button(\"Open in Editor...\", action: openGitConfigFile)\n        }\n        .frame(maxWidth: .infinity)\n    }\n\n    private var gitIgnoreEditor: some View {\n        HStack {\n            Text(\"Ignored file patterns are stored in \\\"\\(resolvedGitIgnorePath)\\\".\")\n                .font(.subheadline)\n                .foregroundStyle(.secondary)\n                .frame(maxWidth: .infinity, alignment: .leading)\n            Button(\"Open in Editor...\", action: openGitIgnoreFile)\n        }\n        .frame(maxWidth: .infinity)\n        .onAppear {\n            Task {\n                resolvedGitIgnorePath = await gitIgnorePath()\n            }\n        }\n    }\n\n    private var gitIgnoreURL: URL {\n        get async throws {\n            if let excludesfile: String = try await gitConfig.get(\n                key: \"core.excludesfile\",\n                global: true\n            ), !excludesfile.isEmpty {\n                if excludesfile.starts(with: \"~/\") {\n                    let relativePath = String(excludesfile.dropFirst(2))\n                    return FileManager.default.homeDirectoryForCurrentUser.appending(path: relativePath)\n                } else if excludesfile.starts(with: \"/\") {\n                    return URL(fileURLWithPath: excludesfile)\n                } else {\n                    return FileManager.default.homeDirectoryForCurrentUser.appending(path: excludesfile)\n                }\n            } else {\n                let defaultURL = FileManager.default.homeDirectoryForCurrentUser.appending(\n                    path: \".gitignore_global\"\n                )\n                await gitConfig.set(key: \"core.excludesfile\", value: \"~/\\(defaultURL.lastPathComponent)\", global: true)\n                return defaultURL\n            }\n        }\n    }\n\n    private func gitIgnorePath() async -> String {\n        do {\n            let url = try await gitIgnoreURL\n            return url.path.replacingOccurrences(of: FileManager.default.homeDirectoryForCurrentUser.path, with: \"~\")\n        } catch {\n            return \"~/.gitignore_global\"\n        }\n    }\n\n    private func openGitConfigFile() {\n        let fileURL = FileManager.default.homeDirectoryForCurrentUser.appending(path: \".gitconfig\")\n\n        if !FileManager.default.fileExists(atPath: fileURL.path) {\n            FileManager.default.createFile(atPath: fileURL.path, contents: nil)\n        }\n\n        NSDocumentController.shared.openDocument(\n            withContentsOf: fileURL,\n            display: true\n        ) { _, _, error in\n            if let error = error {\n                print(\"Failed to open document: \\(error.localizedDescription)\")\n            }\n        }\n    }\n\n    private func openGitIgnoreFile() {\n        Task {\n            do {\n                let fileURL = try await gitIgnoreURL\n\n                // Ensure the file exists\n                if !FileManager.default.fileExists(atPath: fileURL.path) {\n                    FileManager.default.createFile(atPath: fileURL.path, contents: nil)\n                }\n\n                // Open the file in the editor\n                try await NSDocumentController.shared.openDocument(withContentsOf: fileURL, display: true)\n            } catch {\n                print(\"Failed to open document: \\(error.localizedDescription)\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/SourceControlSettings/SourceControlSettingsView.swift",
    "content": "//\n//  SourceControlSettingsView.swift\n//  CodeEdit\n//\n//  Created by Raymond Vleeshouwer on 02/04/23.\n//\n\nimport SwiftUI\n\nstruct SourceControlSettingsView: View {\n    @AppSettings(\\.sourceControl.general)\n    var settings\n\n    @State var selectedTab: String = \"general\"\n\n    var body: some View {\n        SettingsForm {\n            Section {\n                sourceControlIsEnabled\n            } footer: {\n                if settings.sourceControlIsEnabled {\n                    Picker(\"\", selection: $selectedTab) {\n                        Text(\"General\").tag(\"general\")\n                        Text(\"Git\").tag(\"git\")\n                    }\n                    .pickerStyle(.segmented)\n                    .labelsHidden()\n                    .padding(.top, 10)\n                }\n            }\n            if settings.sourceControlIsEnabled {\n                switch selectedTab {\n                case \"general\":\n                    SourceControlGeneralView()\n                case \"git\":\n                    SourceControlGitView()\n                default:\n                    SourceControlGeneralView()\n                }\n            }\n        }\n    }\n\n    private var sourceControlIsEnabled: some View {\n        Toggle(\n            isOn: $settings.sourceControlIsEnabled\n        ) {\n            Label {\n                Text(\"Source Control\")\n                Text(\"\"\"\n                 Back up your files, collaborate with others, and tag your releases. \\\n                 [Learn more...](https://developer.apple.com/documentation/xcode/source-control-management)\n                 \"\"\")\n                .font(.callout)\n             } icon: {\n                FeatureIcon(symbol: \"vault\", color: Color(.systemBlue), size: 26)\n            }\n        }\n        .controlSize(.large)\n    }\n\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/TerminalSettings/Models/TerminalSettings.swift",
    "content": "//\n//  TerminalPreferences.swift\n//  CodeEditModules/Settings\n//\n//  Created by Nanashi Li on 2022/04/08.\n//\n\nimport AppKit\nimport Foundation\n\nextension SettingsData {\n\n    /// The global settings for the terminal emulator\n    struct TerminalSettings: Codable, Hashable, SearchableSettingsPage {\n\n        /// The search keys\n        var searchKeys: [String] {\n            [\n                \"Shell\",\n                \"Use \\\"Option\\\" key as \\\"Meta\\\"\",\n                \"Use text editor font\",\n                \"Font\",\n                \"Font Size\",\n                \"Terminal Cursor Style\",\n                \"Blink Cursor\"\n            ]\n            .map { NSLocalizedString($0, comment: \"\") }\n        }\n\n        /// If true terminal will use editor theme.\n        var useEditorTheme: Bool = true\n\n        /// If true terminal appearance will always be `dark`. Otherwise it adapts to the system setting.\n        var darkAppearance: Bool = false\n\n        /// If true, the terminal uses the background color of the theme, otherwise it is clear\n        var useThemeBackground: Bool = true\n\n        /// If true, the terminal treats the `Option` key as the `Meta` key\n        var optionAsMeta: Bool = false\n\n        /// The selected shell to use.\n        var shell: TerminalShell = .system\n\n        /// The font to use in terminal.\n        var font: TerminalFont = .init()\n\n        // The cursor style to use in terminal\n        var cursorStyle: TerminalCursorStyle = .block\n\n        // Toggle for blinking cursor or not\n        var cursorBlink: Bool = false\n\n        // Use font settings from Text Editing\n        var useTextEditorFont: Bool = true\n\n        /// If `true`, use injection scripts for terminal features like automatic tab title.\n        var useShellIntegration: Bool = true\n\n        /// If `true`, use a login shell.\n        var useLoginShell: Bool = true\n\n        /// Default initializer\n        init() {}\n\n        /// Explicit decoder init for setting default values when key is not present in `JSON`\n        init(from decoder: Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.darkAppearance = try container.decodeIfPresent(Bool.self, forKey: .darkAppearance) ?? false\n            self.optionAsMeta = try container.decodeIfPresent(Bool.self, forKey: .optionAsMeta) ?? false\n            self.shell = try container.decodeIfPresent(TerminalShell.self, forKey: .shell) ?? .system\n            self.font = try container.decodeIfPresent(TerminalFont.self, forKey: .font) ?? .init()\n            self.cursorStyle = try container.decodeIfPresent(\n                TerminalCursorStyle.self,\n                forKey: .cursorStyle\n            ) ?? .block\n            self.cursorBlink = try container.decodeIfPresent(Bool.self, forKey: .cursorBlink) ?? false\n            self.useTextEditorFont = try container.decodeIfPresent(Bool.self, forKey: .useTextEditorFont) ?? true\n            self.useShellIntegration = try container.decodeIfPresent(Bool.self, forKey: .useShellIntegration) ?? true\n            self.useLoginShell = try container.decodeIfPresent(Bool.self, forKey: .useLoginShell) ?? true\n        }\n    }\n\n    /// The shell options.\n    /// - **bash**: uses the default bash shell\n    /// - **zsh**: uses the ZSH shell\n    /// - **system**: uses the system default shell (most likely ZSH)\n    enum TerminalShell: String, Codable, Hashable {\n        case bash\n        case zsh\n        case system\n    }\n\n    enum TerminalCursorStyle: String, Codable, Hashable {\n        case block\n        case underline\n        case bar\n    }\n\n    struct TerminalFont: Codable, Hashable {\n        /// The font size for the custom font\n        var size: Double = 12\n\n        /// The name of the custom font\n        var name: String = \"SF Mono\"\n\n        /// The weight of the custom font\n        var weight: NSFont.Weight = .medium\n\n        /// Default initializer\n        init() {}\n\n        /// Explicit decoder init for setting default values when key is not present in `JSON`\n        init(from decoder: Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.size = try container.decodeIfPresent(Double.self, forKey: .size) ?? size\n            self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? name\n            self.weight = try container.decodeIfPresent(NSFont.Weight.self, forKey: .weight) ?? weight\n        }\n\n        /// Returns an NSFont representation of the current configuration.\n        ///\n        /// Returns the custom font, if enabled and able to be instantiated.\n        /// Otherwise returns a default system font monospaced.\n        var current: NSFont {\n            let customFont = NSFont(name: name, size: size)?.withWeight(weight: weight)\n            return customFont ?? NSFont.monospacedSystemFont(ofSize: size, weight: .medium)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/TerminalSettings/TerminalSettingsView.swift",
    "content": "//\n//  TerminalSettingsView.swift\n//  CodeEdit\n//\n//  Created by Raymond Vleeshouwer on 02/04/23.\n//\n\nimport SwiftUI\n\nstruct TerminalSettingsView: View {\n    @AppSettings(\\.terminal)\n    var settings\n\n    var body: some View {\n        SettingsForm {\n            Section {\n                shellSelector\n                optionAsMetaToggle\n            }\n            Section {\n                useTextEditorFontToggle\n                if !settings.useTextEditorFont {\n                    fontSelector\n                    fontSizeSelector\n                    fontWeightSelector\n                }\n            }\n            Section {\n                cursorStyle\n                cursorBlink\n            }\n            Section {\n                injectionOptions\n                useLoginShell\n            }\n        }\n    }\n}\n\nprivate extension TerminalSettingsView {\n    @ViewBuilder private var shellSelector: some View {\n        Picker(\"Shell\", selection: $settings.shell) {\n            Text(\"System Default\")\n                .tag(SettingsData.TerminalShell.system)\n            Divider()\n            Text(\"Zsh\")\n                .tag(SettingsData.TerminalShell.zsh)\n            Text(\"Bash\")\n                .tag(SettingsData.TerminalShell.bash)\n        }\n    }\n\n    private var cursorStyle: some View {\n        Picker(\"Terminal Cursor Style\", selection: $settings.cursorStyle) {\n            Text(\"Block\")\n                .tag(SettingsData.TerminalCursorStyle.block)\n            Text(\"Underline\")\n                .tag(SettingsData.TerminalCursorStyle.underline)\n            Text(\"Bar\")\n                .tag(SettingsData.TerminalCursorStyle.bar)\n        }\n    }\n\n    private var cursorBlink: some View {\n        Toggle(\"Blink Cursor\", isOn: $settings.cursorBlink)\n    }\n\n    private var optionAsMetaToggle: some View {\n        Toggle(\"Use \\\"Option\\\" key as \\\"Meta\\\"\", isOn: $settings.optionAsMeta)\n    }\n\n    private var useTextEditorFontToggle: some View {\n        Toggle(\"Use text editor font\", isOn: $settings.useTextEditorFont)\n    }\n\n    @ViewBuilder private var fontSelector: some View {\n        MonospacedFontPicker(title: \"Font\", selectedFontName: $settings.font.name)\n    }\n\n    private var fontSizeSelector: some View {\n        Stepper(\n            \"Font Size\",\n            value: $settings.font.size,\n            in: 1...288,\n            step: 1,\n            format: .number\n        )\n    }\n\n    @ViewBuilder private var fontWeightSelector: some View {\n        FontWeightPicker(selection: $settings.font.weight)\n    }\n\n    @ViewBuilder private var injectionOptions: some View {\n        VStack {\n            Toggle(\"Shell Integration\", isOn: $settings.useShellIntegration)\n            // swiftlint:disable:next line_length\n                .help(\"CodeEdit supports integrating with common shells such as Bash and Zsh. This enables features like terminal title detection.\")\n            if !settings.useShellIntegration {\n                HStack {\n                    Image(systemName: \"exclamationmark.triangle.fill\")\n                        .foregroundStyle(Color(NSColor.systemYellow))\n                    Text(\"Warning: Disabling integration disables features such as terminal title detection.\")\n                    Spacer()\n                }\n            }\n        }\n    }\n\n    @ViewBuilder private var useLoginShell: some View {\n        if settings.useShellIntegration {\n            Toggle(\"Use Login Shell\", isOn: $settings.useLoginShell)\n            // swiftlint:disable:next line_length\n                .help(\"Whether or not to use a login shell when starting a terminal session. By default, a login shell is used used similar to Terminal.app.\")\n        } else {\n            EmptyView()\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/TextEditingSettings/InvisiblesSettingsView.swift",
    "content": "//\n//  InvisiblesSettingsView.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 6/13/25.\n//\n\nimport SwiftUI\n\nstruct InvisiblesSettingsView: View {\n    typealias Config = SettingsData.TextEditingSettings.InvisibleCharactersConfig\n\n    @Binding var invisibleCharacters: Config\n\n    @Environment(\\.dismiss)\n    private var dismiss\n\n    var body: some View {\n        VStack(spacing: 0) {\n            Form {\n                Section {\n                    VStack {\n                        Toggle(isOn: $invisibleCharacters.showSpaces) { Text(\"Show Spaces\") }\n                        if invisibleCharacters.showSpaces {\n                            TextField(\n                                text: $invisibleCharacters.spaceReplacement,\n                                prompt: Text(\"Default: \\(Config.default.spaceReplacement)\")\n                            ) {\n                                Text(\"Character used to render spaces\")\n                                    .foregroundStyle(.secondary)\n                                    .font(.caption)\n                            }\n                            .autocorrectionDisabled()\n                        }\n                    }\n\n                    VStack {\n                        Toggle(isOn: $invisibleCharacters.showTabs) { Text(\"Show Tabs\") }\n                        if invisibleCharacters.showTabs {\n                            TextField(\n                                text: $invisibleCharacters.tabReplacement,\n                                prompt: Text(\"Default: \\(Config.default.tabReplacement)\")\n                            ) {\n                                Text(\"Character used to render tabs\")\n                                    .foregroundStyle(.secondary)\n                                    .font(.caption)\n                            }\n                            .autocorrectionDisabled()\n                        }\n                    }\n\n                    VStack {\n                        Toggle(isOn: $invisibleCharacters.showLineEndings) { Text(\"Show Line Endings\") }\n                        if invisibleCharacters.showLineEndings {\n                            TextField(\n                                text: $invisibleCharacters.lineFeedReplacement,\n                                prompt: Text(\"Default: \\(Config.default.lineFeedReplacement)\")\n                            ) {\n                                Text(\"Character used to render line feeds (\\\\n)\")\n                                    .foregroundStyle(.secondary)\n                                    .font(.caption)\n                            }\n                            .autocorrectionDisabled()\n\n                            TextField(\n                                text: $invisibleCharacters.carriageReturnReplacement,\n                                prompt: Text(\"Default: \\(Config.default.carriageReturnReplacement)\")\n                            ) {\n                                Text(\"Character used to render carriage returns (Microsoft-style line endings)\")\n                                    .foregroundStyle(.secondary)\n                                    .font(.caption)\n                            }\n                            .autocorrectionDisabled()\n\n                            TextField(\n                                text: $invisibleCharacters.paragraphSeparatorReplacement,\n                                prompt: Text(\"Default: \\(Config.default.paragraphSeparatorReplacement)\")\n                            ) {\n                                Text(\"Character used to render paragraph separators\")\n                                    .foregroundStyle(.secondary)\n                                    .font(.caption)\n                            }\n                            .autocorrectionDisabled()\n\n                            TextField(\n                                text: $invisibleCharacters.lineSeparatorReplacement,\n                                prompt: Text(\"Default: \\(Config.default.lineSeparatorReplacement)\")\n                            ) {\n                                Text(\"Character used to render line separators\")\n                                    .foregroundStyle(.secondary)\n                                    .font(.caption)\n                            }\n                            .autocorrectionDisabled()\n                        }\n                    }\n                } header: {\n                    Text(\"Invisible Characters\")\n                    Text(\"Toggle whitespace symbols CodeEdit will render with replacement characters.\")\n                }\n                .textFieldStyle(.roundedBorder)\n            }\n            .formStyle(.grouped)\n            Divider()\n            HStack {\n                Spacer()\n                Button {\n                    dismiss()\n                } label: {\n                    Text(\"Done\")\n                        .frame(minWidth: 56)\n                }\n                .buttonStyle(.borderedProminent)\n            }\n            .padding()\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/TextEditingSettings/Models/NSFont+WithWeight.swift",
    "content": "//\n//  NSFont+WithWeight.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 6/17/24.\n//\n\nimport SwiftUI\n\nextension NSFont {\n    /// Rough mapping from behavior of .systemFont(…weight:)\n    /// to NSFontManager's Int-based weight, as of 13.4 Ventura\n    func withWeight(weight: NSFont.Weight) -> NSFont? {\n        let fontManager = NSFontManager.shared\n        var intWeight: Int\n\n        switch weight {\n        case .ultraLight:\n            intWeight=0\n        case .light:\n            intWeight=2\n        case .thin:\n            intWeight=3\n        case .medium:\n            intWeight=6\n        case .semibold:\n            intWeight=8\n        case .bold:\n            intWeight=9\n        case .heavy:\n            intWeight=10\n        case .black:\n            intWeight=15\n        default:\n            intWeight=5\n        }\n\n        return fontManager.font(\n            withFamily: self.familyName ?? \"\",\n            traits: [],\n            weight: intWeight,\n            size: self.pointSize\n        )\n    }\n}\n\nextension NSFont.Weight: @retroactive Codable {\n    public func encode(to encoder: Encoder) throws {\n        var container = encoder.singleValueContainer()\n        try container.encode(self.rawValue)\n    }\n\n    public init(from decoder: Decoder) throws {\n        let container = try decoder.singleValueContainer()\n        let rawValue = try container.decode(CGFloat.self)\n        self = NSFont.Weight(rawValue: rawValue)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/TextEditingSettings/Models/TextEditingSettings.swift",
    "content": "//\n//  TextEditingPreferences.swift\n//  CodeEditModules/Settings\n//\n//  Created by Nanashi Li on 2022/04/08.\n//\n\nimport AppKit\nimport Foundation\n\nextension SettingsData {\n\n    /// The global settings for text editing\n    struct TextEditingSettings: Codable, Hashable, SearchableSettingsPage {\n\n        var searchKeys: [String] {\n            var keys = [\n                \"Prefer Indent Using\",\n                \"Tab Width\",\n                \"Wrap lines to editor width\",\n                \"Editor Overscroll\",\n                \"Font\",\n                \"Font Size\",\n                \"Font Weight\",\n                \"Line Height\",\n                \"Letter Spacing\",\n                \"Autocomplete braces\",\n                \"Enable type-over completion\",\n                \"Bracket Pair Emphasis\",\n                \"Bracket Pair Highlight\",\n                \"Show Gutter\",\n                \"Show Minimap\",\n                \"Reformat at Column\",\n                \"Show Reformatting Guide\",\n                \"Invisibles\",\n                \"Warning Characters\"\n            ]\n            if #available(macOS 14.0, *) {\n                keys.append(\"System Cursor\")\n            }\n            return keys.map { NSLocalizedString($0, comment: \"\") }\n        }\n\n        /// An integer indicating how many spaces a `tab` will appear as visually.\n        var defaultTabWidth: Int = 4\n\n        /// The behavior of a `tab` keypress. If `.tab`, will insert a tab character. If `.spaces` will insert\n        /// `.spaceCount` spaces instead.\n        var indentOption: IndentOption = IndentOption(indentType: .spaces, spaceCount: 4)\n\n        /// The font to use in editor.\n        var font: EditorFont = .init()\n\n        /// A flag indicating whether type-over completion is enabled\n        var enableTypeOverCompletion: Bool = true\n\n        /// A flag indicating whether braces are automatically completed\n        var autocompleteBraces: Bool = true\n\n        /// A flag indicating whether to wrap lines to editor width\n        var wrapLinesToEditorWidth: Bool = true\n\n        /// The percentage of overscroll to apply to the text view\n        var overscroll: OverscrollOption = .medium\n\n        /// A multiplier for setting the line height. Defaults to `1.2`\n        var lineHeightMultiple: Double = 1.2\n\n        /// A multiplier for setting the letter spacing, `1` being no spacing and\n        /// `2` is one character of spacing between letters, defaults to `1`.\n        var letterSpacing: Double = 1.0\n\n        /// The behavior of bracket pair highlights.\n        var bracketEmphasis: BracketPairEmphasis = BracketPairEmphasis()\n\n        /// Use the system cursor for the source editor.\n        var useSystemCursor: Bool = true\n\n        /// Toggle the gutter in the editor.\n        var showGutter: Bool = true\n\n        /// Toggle the minimap in the editor.\n        var showMinimap: Bool = true\n\n        /// Toggle the code folding ribbon.\n        var showFoldingRibbon: Bool = true\n\n        /// The column at which to reformat text\n        var reformatAtColumn: Int = 80\n\n        /// Show the reformatting guide in the editor\n        var showReformattingGuide: Bool = false\n\n        var invisibleCharacters: InvisibleCharactersConfig = .default\n\n        /// Map of unicode character codes to a note about them\n        var warningCharacters: WarningCharacters = .default\n\n        /// Default initializer\n        init() {\n            self.populateCommands()\n        }\n\n        /// Explicit decoder init for setting default values when key is not present in `JSON`\n        init(from decoder: Decoder) throws { // swiftlint:disable:this function_body_length\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.defaultTabWidth = try container.decodeIfPresent(Int.self, forKey: .defaultTabWidth) ?? 4\n            self.indentOption = try container.decodeIfPresent(\n                IndentOption.self,\n                forKey: .indentOption\n            ) ?? IndentOption(indentType: .spaces, spaceCount: 4)\n            self.font = try container.decodeIfPresent(EditorFont.self, forKey: .font) ?? .init()\n            self.enableTypeOverCompletion = try container.decodeIfPresent(\n                Bool.self,\n                forKey: .enableTypeOverCompletion\n            ) ?? true\n            self.autocompleteBraces = try container.decodeIfPresent(\n                Bool.self,\n                forKey: .autocompleteBraces\n            ) ?? true\n            self.wrapLinesToEditorWidth = try container.decodeIfPresent(\n                Bool.self,\n                forKey: .wrapLinesToEditorWidth\n            ) ?? true\n            self.overscroll = try container.decodeIfPresent(\n                OverscrollOption.self,\n                forKey: .overscroll\n            ) ?? .medium\n            self.lineHeightMultiple = try container.decodeIfPresent(\n                Double.self,\n                forKey: .lineHeightMultiple\n            ) ?? 1.2\n            self.letterSpacing = try container.decodeIfPresent(\n                Double.self,\n                forKey: .letterSpacing\n            ) ?? 1\n            self.bracketEmphasis = try container.decodeIfPresent(\n                BracketPairEmphasis.self,\n                forKey: .bracketEmphasis\n            ) ?? BracketPairEmphasis()\n            if #available(macOS 14, *) {\n                self.useSystemCursor = try container.decodeIfPresent(Bool.self, forKey: .useSystemCursor) ?? true\n            } else {\n                self.useSystemCursor = false\n            }\n\n            self.showGutter = try container.decodeIfPresent(Bool.self, forKey: .showGutter) ?? true\n            self.showMinimap = try container.decodeIfPresent(Bool.self, forKey: .showMinimap) ?? true\n            self.showFoldingRibbon = try container.decodeIfPresent(Bool.self, forKey: .showFoldingRibbon) ?? true\n            self.reformatAtColumn = try container.decodeIfPresent(Int.self, forKey: .reformatAtColumn) ?? 80\n            self.showReformattingGuide = try container.decodeIfPresent(\n                Bool.self,\n                forKey: .showReformattingGuide\n            ) ?? false\n            self.invisibleCharacters = try container.decodeIfPresent(\n                InvisibleCharactersConfig.self,\n                forKey: .invisibleCharacters\n            ) ?? .default\n            self.warningCharacters = try container.decodeIfPresent(\n                WarningCharacters.self,\n                forKey: .warningCharacters\n            ) ?? .default\n\n            self.populateCommands()\n        }\n\n        /// Adds toggle-able preferences to the command palette via shared `CommandManager`\n        private func populateCommands() {\n            let mgr = CommandManager.shared\n\n            mgr.addCommand(\n                name: \"Toggle Type-Over Completion\",\n                title: \"Toggle Type-Over Completion\",\n                id: \"prefs.text_editing.type_over_completion\",\n                command: {\n                    Settings[\\.textEditing].enableTypeOverCompletion.toggle()\n                }\n            )\n\n            mgr.addCommand(\n                name: \"Toggle Autocomplete Braces\",\n                title: \"Toggle Autocomplete Braces\",\n                id: \"prefs.text_editing.autocomplete_braces\",\n                command: {\n                    Settings[\\.textEditing].autocompleteBraces.toggle()\n                }\n            )\n\n            mgr.addCommand(\n                name: \"Toggle Word Wrap\",\n                title: \"Toggle Word Wrap\",\n                id: \"prefs.text_editing.wrap_lines_to_editor_width\",\n                command: {\n                    Settings[\\.textEditing].wrapLinesToEditorWidth.toggle()\n                }\n            )\n\n            mgr.addCommand(name: \"Toggle Minimap\", title: \"Toggle Minimap\", id: \"prefs.text_editing.toggle_minimap\") {\n                Settings[\\.textEditing].showMinimap.toggle()\n            }\n\n            mgr.addCommand(name: \"Toggle Gutter\", title: \"Toggle Gutter\", id: \"prefs.text_editing.toggle_gutter\") {\n                Settings[\\.textEditing].showGutter.toggle()\n            }\n\n            mgr.addCommand(\n                name: \"Toggle Folding Ribbon\",\n                title: \"Toggle Folding Ribbon\",\n                id: \"prefs.text_editing.toggle_folding_ribbon\"\n            ) {\n                Settings[\\.textEditing].showFoldingRibbon.toggle()\n            }\n        }\n\n        struct IndentOption: Codable, Hashable {\n            var indentType: IndentType\n            // Kept even when `indentType` is `.tab` to retain the user's\n            // settings when changing `indentType`.\n            var spaceCount: Int = 4\n\n            enum IndentType: String, Codable {\n                case tab\n                case spaces\n            }\n        }\n\n        struct BracketPairEmphasis: Codable, Hashable {\n            /// The type of highlight to use\n            var highlightType: HighlightType = .flash\n            var useCustomColor: Bool = false\n            /// The color to use for the highlight.\n            var color: Theme.Attributes = Theme.Attributes(color: \"FFFFFF\", bold: false, italic: false)\n\n            enum HighlightType: String, Codable {\n                case disabled\n                case bordered\n                case flash\n                case underline\n            }\n        }\n\n        enum OverscrollOption: String, Codable {\n            case none\n            case small\n            case medium\n            case large\n\n            var overscrollPercentage: CGFloat {\n                switch self {\n                case .none: return 0\n                case .small: return 0.25\n                case .medium: return 0.5\n                case .large: return 0.75\n                }\n            }\n        }\n\n        struct InvisibleCharactersConfig: Equatable, Hashable, Codable {\n            static var `default`: InvisibleCharactersConfig = {\n                InvisibleCharactersConfig(\n                    enabled: false,\n                    showSpaces: true,\n                    showTabs: true,\n                    showLineEndings: true\n                )\n            }()\n\n            var enabled: Bool\n\n            var showSpaces: Bool\n            var showTabs: Bool\n            var showLineEndings: Bool\n\n            var spaceReplacement: String = \"·\"\n            var tabReplacement: String = \"→\"\n\n            // Controlled by `showLineEndings`\n            var carriageReturnReplacement: String = \"↵\"\n            var lineFeedReplacement: String = \"¬\"\n            var paragraphSeparatorReplacement: String = \"¶\"\n            var lineSeparatorReplacement: String = \"⏎\"\n        }\n\n        struct WarningCharacters: Equatable, Hashable, Codable {\n            static let `default`: WarningCharacters = WarningCharacters(enabled: true, characters: [\n                0x0003: \"End of text\",\n\n                0x00A0: \"Non-breaking space\",\n                0x202F: \"Narrow non-breaking space\",\n                0x200B: \"Zero-width space\",\n                0x200C: \"Zero-width non-joiner\",\n                0x2029: \"Paragraph separator\",\n\n                0x2013: \"Em-dash\",\n                0x00AD: \"Soft hyphen\",\n\n                0x2018: \"Left single quote\",\n                0x2019: \"Right single quote\",\n                0x201C: \"Left double quote\",\n                0x201D: \"Right double quote\",\n\n                0x037E: \"Greek Question Mark\"\n            ])\n\n            var enabled: Bool\n            var characters: [UInt16: String]\n        }\n    }\n\n    struct EditorFont: Codable, Hashable {\n        /// The font size for the font\n        var size: Double = 12\n\n        /// The name of the custom font\n        var name: String = \"SF Mono\"\n\n        /// The weight of the custom font\n        var weight: NSFont.Weight = .medium\n\n        /// Default initializer\n        init() {}\n\n        /// Explicit decoder init for setting default values when key is not present in `JSON`\n        init(from decoder: Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.size = try container.decodeIfPresent(Double.self, forKey: .size) ?? size\n            self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? name\n            self.weight = try container.decodeIfPresent(NSFont.Weight.self, forKey: .weight) ?? weight\n        }\n\n        /// Returns an NSFont representation of the current configuration.\n        ///\n        /// Returns the custom font, if enabled and able to be instantiated.\n        /// Otherwise returns a default system font monospaced.\n        var current: NSFont {\n            let customFont = NSFont(name: name, size: size)?.withWeight(weight: weight)\n            return customFont ?? NSFont.monospacedSystemFont(ofSize: size, weight: .medium)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/TextEditingSettings/TextEditingSettingsView.swift",
    "content": "//\n//  TextEditingSettingsView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 4/2/23.\n//\n\nimport SwiftUI\n\n/// A view that implements the `Text Editing` settings page\nstruct TextEditingSettingsView: View {\n    @AppSettings(\\.textEditing)\n    var textEditing\n\n    @State private var isShowingInvisibleCharacterSettings = false\n    @State private var isShowingWarningCharactersSettings = false\n\n    var body: some View {\n        SettingsForm {\n            Section {\n                indentOption\n                defaultTabWidth\n                wrapLinesToEditorWidth\n                useSystemCursor\n                overscroll\n            }\n            Section {\n                showGutter\n                showMinimap\n                showFoldingRibbon\n                reformatSettings\n            }\n            Section {\n                fontSelector\n                fontSizeSelector\n                fontWeightSelector\n                lineHeight\n                letterSpacing\n            }\n            Section {\n                autocompleteBraces\n                enableTypeOverCompletion\n            }\n            Section {\n                bracketPairHighlight\n            }\n            Section {\n                invisibles\n                warningCharacters\n            }\n        }\n    }\n}\n\nprivate extension TextEditingSettingsView {\n    @ViewBuilder private var fontSelector: some View {\n        MonospacedFontPicker(title: \"Font\", selectedFontName: $textEditing.font.name)\n    }\n\n    @ViewBuilder private var fontSizeSelector: some View {\n        Stepper(\n            \"Font Size\",\n            value: $textEditing.font.size,\n            in: 1...288,\n            step: 1,\n            format: .number\n        )\n    }\n\n    @ViewBuilder private var fontWeightSelector: some View {\n        FontWeightPicker(selection: $textEditing.font.weight)\n    }\n\n    @ViewBuilder private var autocompleteBraces: some View {\n        Toggle(isOn: $textEditing.autocompleteBraces) {\n            Text(\"Autocomplete braces\")\n            Text(\"Automatically insert closing braces (\\\"}\\\")\")\n        }\n    }\n\n    @ViewBuilder private var enableTypeOverCompletion: some View {\n        Toggle(\"Enable type-over completion\", isOn: $textEditing.enableTypeOverCompletion)\n    }\n\n    @ViewBuilder private var wrapLinesToEditorWidth: some View {\n        Toggle(\"Wrap lines to editor width\", isOn: $textEditing.wrapLinesToEditorWidth)\n    }\n\n    @ViewBuilder private var useSystemCursor: some View {\n        if #available(macOS 14, *) {\n            Toggle(\"Use System Cursor\", isOn: $textEditing.useSystemCursor)\n        } else {\n            EmptyView()\n        }\n    }\n\n    @ViewBuilder private var overscroll: some View {\n        Group {\n            Picker(\n                \"Editor Overscroll\",\n                selection: $textEditing.overscroll\n            ) {\n                Text(\"None\")\n                    .tag(SettingsData.TextEditingSettings.OverscrollOption.none)\n                Divider()\n                Text(\"Small\")\n                    .tag(\n                        SettingsData.TextEditingSettings.OverscrollOption.small\n                    )\n                Text(\"Medium\")\n                    .tag(\n                        SettingsData.TextEditingSettings.OverscrollOption.medium\n                    )\n                Text(\"Large\")\n                    .tag(\n                        SettingsData.TextEditingSettings.OverscrollOption.large\n                    )\n            }\n        }\n    }\n\n    @ViewBuilder private var lineHeight: some View {\n        Stepper(\n            \"Line Height\",\n            value: $textEditing.lineHeightMultiple,\n            in: 0.75...2.0,\n            step: 0.05,\n            format: .number\n        )\n    }\n\n    @ViewBuilder private var indentOption: some View {\n        Group {\n            Picker(\"Prefer Indent Using\", selection: $textEditing.indentOption.indentType) {\n                Text(\"Tabs\")\n                    .tag(SettingsData.TextEditingSettings.IndentOption.IndentType.tab)\n                Text(\"Spaces\")\n                    .tag(SettingsData.TextEditingSettings.IndentOption.IndentType.spaces)\n            }\n            if textEditing.indentOption.indentType == .spaces {\n                HStack {\n                    Stepper(\n                        \"Indent Width\",\n                        value: Binding<Double>(\n                            get: { Double(textEditing.indentOption.spaceCount) },\n                            set: { textEditing.indentOption.spaceCount = Int($0) }\n                        ),\n                        in: 0...10,\n                        step: 1,\n                        format: .number\n                    )\n                    Text(\"spaces\")\n                        .foregroundColor(.secondary)\n                }\n                .help(\"The number of spaces to insert when the tab key is pressed.\")\n            }\n        }\n    }\n\n    @ViewBuilder private var defaultTabWidth: some View {\n        HStack(alignment: .top) {\n            Stepper(\n                \"Tab Width\",\n                value: Binding<Double>(\n                    get: { Double(textEditing.defaultTabWidth) },\n                    set: { textEditing.defaultTabWidth = Int($0) }\n                ),\n                in: 1...16,\n                step: 1,\n                format: .number\n            )\n            Text(\"spaces\")\n                .foregroundColor(.secondary)\n        }\n        .help(\"The visual width of tabs.\")\n    }\n\n    @ViewBuilder private var letterSpacing: some View {\n        Stepper(\n            \"Letter Spacing\",\n            value: $textEditing.letterSpacing,\n            in: 0.5...2.0,\n            step: 0.05,\n            format: .number\n        )\n    }\n\n    @ViewBuilder private var bracketPairHighlight: some View {\n        Group {\n            Picker(\n                \"Bracket Pair Highlight\",\n                selection: $textEditing.bracketEmphasis.highlightType\n            ) {\n                Text(\"Disabled\").tag(SettingsData.TextEditingSettings.BracketPairEmphasis.HighlightType.disabled)\n                Divider()\n                Text(\"Bordered\").tag(SettingsData.TextEditingSettings.BracketPairEmphasis.HighlightType.bordered)\n                Text(\"Flash\").tag(SettingsData.TextEditingSettings.BracketPairEmphasis.HighlightType.flash)\n                Text(\"Underline\").tag(SettingsData.TextEditingSettings.BracketPairEmphasis.HighlightType.underline)\n            }\n            if [.bordered, .underline].contains(textEditing.bracketEmphasis.highlightType) {\n                Toggle(\"Use Custom Color\", isOn: $textEditing.bracketEmphasis.useCustomColor)\n                SettingsColorPicker(\n                    \"Bracket Pair Highlight Color\",\n                    color: $textEditing.bracketEmphasis.color.swiftColor\n                )\n                .foregroundColor(\n                    textEditing.bracketEmphasis.useCustomColor\n                        ? Color(.labelColor)\n                        : Color(.secondaryLabelColor)\n                )\n                .disabled(!textEditing.bracketEmphasis.useCustomColor)\n            }\n        }\n    }\n\n    @ViewBuilder private var showGutter: some View {\n        Toggle(\"Show Gutter\", isOn: $textEditing.showGutter)\n            .help(\"The gutter displays line numbers and code folding regions.\")\n    }\n\n    @ViewBuilder private var showMinimap: some View {\n        Toggle(\"Show Minimap\", isOn: $textEditing.showMinimap)\n            // swiftlint:disable:next line_length\n            .help(\"The minimap gives you a high-level summary of your source code, with controls to quickly navigate your document.\")\n    }\n\n    @ViewBuilder private var showFoldingRibbon: some View {\n        Toggle(\"Show Code Folding Ribbon\", isOn: $textEditing.showFoldingRibbon)\n            .disabled(!textEditing.showGutter) // Disabled when the gutter is disabled\n            // swiftlint:disable:next line_length\n            .help(\"The code folding ribbon lets you fold regions of code. When the gutter is disabled, the folding ribbon is disabled.\")\n    }\n\n    @ViewBuilder private var reformatSettings: some View {\n        Toggle(\"Show Reformatting Guide\", isOn: $textEditing.showReformattingGuide)\n            .help(\"Shows a vertical guide at the reformat column.\")\n\n        Stepper(\n            \"Reformat at Column\",\n            value: Binding<Double>(\n                get: { Double(textEditing.reformatAtColumn) },\n                set: { textEditing.reformatAtColumn = Int($0) }\n            ),\n            in: 40...200,\n            step: 1,\n            format: .number\n        )\n        .help(\"The column at which text should be reformatted.\")\n    }\n\n    @ViewBuilder private var invisibles: some View {\n        HStack {\n            Text(\"Show Invisible Characters\")\n            Spacer()\n            Toggle(isOn: $textEditing.invisibleCharacters.enabled, label: { EmptyView() })\n            Button {\n                isShowingInvisibleCharacterSettings = true\n            } label: {\n                Text(\"Configure...\")\n            }\n            .disabled(textEditing.invisibleCharacters.enabled == false)\n        }\n        .contentShape(Rectangle())\n        .onTapGesture {\n            if textEditing.invisibleCharacters.enabled {\n                isShowingInvisibleCharacterSettings = true\n            }\n        }\n        .sheet(isPresented: $isShowingInvisibleCharacterSettings) {\n            InvisiblesSettingsView(invisibleCharacters: $textEditing.invisibleCharacters)\n        }\n    }\n\n    @ViewBuilder private var warningCharacters: some View {\n        HStack {\n            Text(\"Show Warning Characters\")\n            Spacer()\n            Toggle(isOn: $textEditing.warningCharacters.enabled, label: { EmptyView() })\n            Button {\n                isShowingWarningCharactersSettings = true\n            } label: {\n                Text(\"Configure...\")\n            }\n            .disabled(textEditing.warningCharacters.enabled == false)\n        }\n        .contentShape(Rectangle())\n        .onTapGesture {\n            if textEditing.warningCharacters.enabled {\n                isShowingWarningCharactersSettings = true\n            }\n        }\n        .sheet(isPresented: $isShowingWarningCharactersSettings) {\n            WarningCharactersView(warningCharacters: $textEditing.warningCharacters)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/ThemeSettings/Models/Theme+FuzzySearchable.swift",
    "content": "//\n//  Theme+FuzzySearchable.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 14.08.24.\n//\n\nimport Foundation\n\nextension Theme: FuzzySearchable {\n    var searchableString: String {\n        return id\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/ThemeSettings/Models/Theme.swift",
    "content": "//\n//  Theme.swift\n//  CodeEditModules/Settings\n//\n//  Created by Lukas Pistrol on 31.03.22.\n//\n\nimport SwiftUI\nimport CodeEditSourceEditor\n\n// swiftlint:disable file_length\n\n/// # Theme\n///\n/// The model structure of themes for the editor & terminal emulator\nstruct Theme: Identifiable, Codable, Equatable, Hashable, Loopable {\n    enum CodingKeys: String, CodingKey {\n        case author, license, distributionURL, name, displayName, editor, terminal, version\n        case appearance = \"type\"\n        case metadataDescription = \"description\"\n    }\n\n    static func == (lhs: Theme, rhs: Theme) -> Bool {\n        lhs.id == rhs.id\n    }\n\n    /// The `id` of the theme\n    var id: String { self.name }\n\n    /// The `author` of the theme\n    var author: String\n\n    /// The `license` of the theme\n    var license: String\n\n    /// A short `description` of the theme\n    var metadataDescription: String\n\n    /// An URL for reference\n    var distributionURL: String\n\n    /// If the theme is bundled with CodeEdit or not\n    var isBundled: Bool = false\n\n    /// The URL for the theme file\n    var fileURL: URL?\n\n    /// The `unique name` of the theme\n    var name: String\n\n    /// The `display name` of the theme\n    var displayName: String\n\n    /// The `version` of the theme\n    var version: String\n\n    /// The ``ThemeType`` of the theme\n    ///\n    /// Appears as `\"type\"` in the `settings.json`\n    var appearance: ThemeType\n\n    /// Editor colors of the theme\n    var editor: EditorColors\n\n    /// Terminal colors of the theme\n    var terminal: TerminalColors\n\n    init(\n        editor: EditorColors,\n        terminal: TerminalColors,\n        author: String,\n        license: String,\n        metadataDescription: String,\n        distributionURL: String,\n        isBundled: Bool,\n        name: String,\n        displayName: String,\n        appearance: ThemeType,\n        version: String\n    ) {\n        self.author = author\n        self.license = license\n        self.metadataDescription = metadataDescription\n        self.distributionURL = distributionURL\n        self.isBundled = isBundled\n        self.name = name\n        self.displayName = displayName\n        self.appearance = appearance\n        self.version = version\n        self.editor = editor\n        self.terminal = terminal\n    }\n}\n\nextension Theme {\n    /// The type of the theme\n    /// - **dark**: this is a theme for dark system appearance\n    /// - **light**: this is a theme for light system appearance\n    enum ThemeType: String, Codable, Hashable {\n        case dark\n        case light\n    }\n}\n\n// MARK: - Attributes\nextension Theme {\n    /// Attributes of a certain field\n    ///\n    /// As of now it only includes the colors `hex` string and\n    /// an accessor for a `SwiftUI` `Color`.\n    struct Attributes: Codable, Equatable, Hashable, Loopable {\n\n        /// The 24-bit hex string of the color (e.g. #123456)\n        var color: String\n        var bold: Bool\n        var italic: Bool\n\n        init(color: String, bold: Bool = false, italic: Bool = false) {\n            self.color = color\n            self.bold = bold\n            self.italic = italic\n        }\n\n        init(from decoder: Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.color = try container.decode(String.self, forKey: .color)\n            self.bold = try container.decodeIfPresent(Bool.self, forKey: .bold) ?? false\n            self.italic = try container.decodeIfPresent(Bool.self, forKey: .italic) ?? false\n        }\n\n        func encode(to encoder: Encoder) throws {\n            var container = encoder.container(keyedBy: CodingKeys.self)\n            try container.encode(color, forKey: .color)\n\n            if bold {\n                try container.encode(bold, forKey: .bold)\n            }\n\n            if italic {\n                try container.encode(italic, forKey: .italic)\n            }\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case color\n            case bold\n            case italic\n        }\n\n        /// The `SwiftUI` of ``color``\n        var swiftColor: Color {\n            get {\n                Color(hex: color)\n            }\n            set {\n                self.color = newValue.hexString\n            }\n        }\n\n        /// The `NSColor` of ``color``\n        var nsColor: NSColor {\n            get {\n                NSColor(hex: color)\n            }\n            set {\n                self.color = newValue.hexString\n            }\n        }\n    }\n}\n\nextension Theme {\n    /// The editor colors of the theme\n    struct EditorColors: Codable, Hashable, Loopable {\n\n        var editorTheme: EditorTheme {\n            get {\n                .init(\n                    text: .init(color: text.nsColor),\n                    insertionPoint: insertionPoint.nsColor,\n                    invisibles: .init(color: invisibles.nsColor),\n                    background: background.nsColor,\n                    lineHighlight: lineHighlight.nsColor,\n                    selection: selection.nsColor,\n                    keywords: .init(color: keywords.nsColor),\n                    commands: .init(color: commands.nsColor),\n                    types: .init(color: types.nsColor),\n                    attributes: .init(color: attributes.nsColor),\n                    variables: .init(color: variables.nsColor),\n                    values: .init(color: values.nsColor),\n                    numbers: .init(color: numbers.nsColor),\n                    strings: .init(color: strings.nsColor),\n                    characters: .init(color: characters.nsColor),\n                    comments: .init(color: comments.nsColor)\n                )\n            }\n            set {\n                self.text.nsColor = newValue.text.color\n                self.insertionPoint.nsColor = newValue.insertionPoint\n                self.invisibles.nsColor = newValue.invisibles.color\n                self.background.nsColor = newValue.background\n                self.lineHighlight.nsColor = newValue.lineHighlight\n                self.selection.nsColor = newValue.selection\n                self.keywords.nsColor = newValue.keywords.color\n                self.commands.nsColor = newValue.commands.color\n                self.types.nsColor = newValue.types.color\n                self.attributes.nsColor = newValue.attributes.color\n                self.variables.nsColor = newValue.variables.color\n                self.values.nsColor = newValue.values.color\n                self.numbers.nsColor = newValue.numbers.color\n                self.strings.nsColor = newValue.strings.color\n                self.characters.nsColor = newValue.characters.color\n                self.comments.nsColor = newValue.comments.color\n            }\n        }\n\n        var text: Attributes\n        var insertionPoint: Attributes\n        var invisibles: Attributes\n        var background: Attributes\n        var lineHighlight: Attributes\n        var selection: Attributes\n        var keywords: Attributes\n        var commands: Attributes\n        var types: Attributes\n        var attributes: Attributes\n        var variables: Attributes\n        var values: Attributes\n        var numbers: Attributes\n        var strings: Attributes\n        var characters: Attributes\n        var comments: Attributes\n\n        /// Allows to look up properties by their name\n        ///\n        /// **Example:**\n        /// ```swift\n        /// editor[\"text\"]\n        /// // equal to calling\n        /// editor.text\n        /// ```\n        subscript(key: String) -> Attributes {\n            get {\n                switch key {\n                case \"text\": return self.text\n                case \"insertionPoint\": return self.insertionPoint\n                case \"invisibles\": return self.invisibles\n                case \"background\": return self.background\n                case \"lineHighlight\": return self.lineHighlight\n                case \"selection\": return self.selection\n                case \"keywords\": return self.keywords\n                case \"commands\": return self.commands\n                case \"types\": return self.types\n                case \"attributes\": return self.attributes\n                case \"variables\": return self.variables\n                case \"values\": return self.values\n                case \"numbers\": return self.numbers\n                case \"strings\": return self.strings\n                case \"characters\": return self.characters\n                case \"comments\": return self.comments\n                default: fatalError(\"Invalid key\")\n                }\n            }\n            set {\n                switch key {\n                case \"text\": self.text = newValue\n                case \"insertionPoint\": self.insertionPoint = newValue\n                case \"invisibles\": self.invisibles = newValue\n                case \"background\": self.background = newValue\n                case \"lineHighlight\": self.lineHighlight = newValue\n                case \"selection\": self.selection = newValue\n                case \"keywords\": self.keywords = newValue\n                case \"commands\": self.commands = newValue\n                case \"types\": self.types = newValue\n                case \"attributes\": self.attributes = newValue\n                case \"variables\": self.variables = newValue\n                case \"values\": self.values = newValue\n                case \"numbers\": self.numbers = newValue\n                case \"strings\": self.strings = newValue\n                case \"characters\": self.characters = newValue\n                case \"comments\": self.comments = newValue\n                default: fatalError(\"Invalid key\")\n                }\n            }\n        }\n\n        init(\n            text: Attributes,\n            insertionPoint: Attributes,\n            invisibles: Attributes,\n            background: Attributes,\n            lineHighlight: Attributes,\n            selection: Attributes,\n            keywords: Attributes,\n            commands: Attributes,\n            types: Attributes,\n            attributes: Attributes,\n            variables: Attributes,\n            values: Attributes,\n            numbers: Attributes,\n            strings: Attributes,\n            characters: Attributes,\n            comments: Attributes\n        ) {\n            self.text = text\n            self.insertionPoint = insertionPoint\n            self.invisibles = invisibles\n            self.background = background\n            self.lineHighlight = lineHighlight\n            self.selection = selection\n            self.keywords = keywords\n            self.commands = commands\n            self.types = types\n            self.attributes = attributes\n            self.variables = variables\n            self.values = values\n            self.numbers = numbers\n            self.strings = strings\n            self.characters = characters\n            self.comments = comments\n        }\n    }\n}\n\nextension Theme {\n    /// The terminal emulator colors of the theme\n    struct TerminalColors: Codable, Hashable, Loopable {\n        var text: Attributes\n        var boldText: Attributes\n        var cursor: Attributes\n        var background: Attributes\n        var selection: Attributes\n        var black: Attributes\n        var red: Attributes\n        var green: Attributes\n        var yellow: Attributes\n        var blue: Attributes\n        var magenta: Attributes\n        var cyan: Attributes\n        var white: Attributes\n        var brightBlack: Attributes\n        var brightRed: Attributes\n        var brightGreen: Attributes\n        var brightYellow: Attributes\n        var brightBlue: Attributes\n        var brightMagenta: Attributes\n        var brightCyan: Attributes\n        var brightWhite: Attributes\n\n        var ansiColors: [String] {\n            [\n                black.color,\n                red.color,\n                green.color,\n                yellow.color,\n                blue.color,\n                magenta.color,\n                cyan.color,\n                white.color,\n                brightBlack.color,\n                brightRed.color,\n                brightGreen.color,\n                brightYellow.color,\n                brightBlue.color,\n                brightMagenta.color,\n                brightCyan.color,\n                brightWhite.color,\n            ]\n        }\n\n        /// Allows to look up properties by their name\n        ///\n        /// **Example:**\n        /// ```swift\n        /// terminal[\"text\"]\n        /// // equal to calling\n        /// terminal.text\n        /// ```\n        subscript(key: String) -> Attributes {\n            get {\n                switch key {\n                case \"text\": return self.text\n                case \"boldText\": return self.boldText\n                case \"cursor\": return self.cursor\n                case \"background\": return self.background\n                case \"selection\": return self.selection\n                case \"black\": return self.black\n                case \"red\": return self.red\n                case \"green\": return self.green\n                case \"yellow\": return self.yellow\n                case \"blue\": return self.blue\n                case \"magenta\": return self.magenta\n                case \"cyan\": return self.cyan\n                case \"white\": return self.white\n                case \"brightBlack\": return self.brightBlack\n                case \"brightRed\": return self.brightRed\n                case \"brightGreen\": return self.brightGreen\n                case \"brightYellow\": return self.brightYellow\n                case \"brightBlue\": return self.brightBlue\n                case \"brightMagenta\": return self.brightMagenta\n                case \"brightCyan\": return self.brightCyan\n                case \"brightWhite\": return self.brightWhite\n                default: fatalError(\"Invalid key\")\n                }\n            }\n            set {\n                switch key {\n                case \"text\": self.text = newValue\n                case \"boldText\": self.boldText = newValue\n                case \"cursor\": self.cursor = newValue\n                case \"background\": self.background = newValue\n                case \"selection\": self.selection = newValue\n                case \"black\": self.black = newValue\n                case \"red\": self.red = newValue\n                case \"green\": self.green = newValue\n                case \"yellow\": self.yellow = newValue\n                case \"blue\": self.blue = newValue\n                case \"magenta\": self.magenta = newValue\n                case \"cyan\": self.cyan = newValue\n                case \"white\": self.white = newValue\n                case \"brightBlack\": self.brightBlack = newValue\n                case \"brightRed\": self.brightRed = newValue\n                case \"brightGreen\": self.brightGreen = newValue\n                case \"brightYellow\": self.brightYellow = newValue\n                case \"brightBlue\": self.brightBlue = newValue\n                case \"brightMagenta\": self.brightMagenta = newValue\n                case \"brightCyan\": self.brightCyan = newValue\n                case \"brightWhite\": self.brightWhite = newValue\n                default: fatalError(\"Invalid key\")\n                }\n            }\n        }\n\n        init(\n            text: Attributes,\n            boldText: Attributes,\n            cursor: Attributes,\n            background: Attributes,\n            selection: Attributes,\n            black: Attributes,\n            red: Attributes,\n            green: Attributes,\n            yellow: Attributes,\n            blue: Attributes,\n            magenta: Attributes,\n            cyan: Attributes,\n            white: Attributes,\n            brightBlack: Attributes,\n            brightRed: Attributes,\n            brightGreen: Attributes,\n            brightYellow: Attributes,\n            brightBlue: Attributes,\n            brightMagenta: Attributes,\n            brightCyan: Attributes,\n            brightWhite: Attributes\n        ) {\n            self.text = text\n            self.boldText = boldText\n            self.cursor = cursor\n            self.background = background\n            self.selection = selection\n            self.black = black\n            self.red = red\n            self.green = green\n            self.yellow = yellow\n            self.blue = blue\n            self.magenta = magenta\n            self.cyan = cyan\n            self.white = white\n            self.brightBlack = brightBlack\n            self.brightRed = brightRed\n            self.brightGreen = brightGreen\n            self.brightYellow = brightYellow\n            self.brightBlue = brightBlue\n            self.brightMagenta = brightMagenta\n            self.brightCyan = brightCyan\n            self.brightWhite = brightWhite\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/ThemeSettings/Models/ThemeModel+CRUD.swift",
    "content": "//\n//  ThemeModel+CRUD.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 6/18/24.\n//\n\nimport SwiftUI\nimport UniformTypeIdentifiers\n\nextension ThemeModel {\n    /// Loads a theme from a given url and appends it to ``themes``.\n    /// - Parameter url: The URL of the theme\n    /// - Returns: A ``Theme``\n    private func load(from url: URL) throws -> Theme? {\n        do {\n            // get the data from the provided file\n            let json = try Data(contentsOf: url)\n            // decode the json into ``Theme``\n            let theme = try JSONDecoder().decode(Theme.self, from: json)\n            return theme\n        } catch {\n            print(error)\n            return nil\n        }\n    }\n\n    /// Loads all available themes from `~/Library/Application Support/CodeEdit/Themes/`\n    ///\n    /// If no themes are available, it will create a default theme and save\n    /// it to the location mentioned above.\n    ///\n    /// When overrides are found in `~/Library/Application Support/CodeEdit/settings.json`\n    /// they are applied to the loaded themes without altering the original\n    /// the files in `~/Library/Application Support/CodeEdit/Themes/`.\n    func loadThemes() throws { // swiftlint:disable:this function_body_length\n        if let bundledThemesURL = bundledThemesURL {\n            // remove all themes from memory\n            themes.removeAll()\n\n            var isDir: ObjCBool = false\n\n            // check if a themes directory exists, otherwise create one\n            if !filemanager.fileExists(atPath: themesURL.path, isDirectory: &isDir) {\n                try filemanager.createDirectory(at: themesURL, withIntermediateDirectories: true)\n            }\n\n            // get all URLs in users themes folder that end with `.cetheme`\n            let userDefinedThemeFilenames = try filemanager.contentsOfDirectory(atPath: themesURL.path).filter {\n                $0.contains(\".cetheme\")\n            }\n            let userDefinedThemeURLs = userDefinedThemeFilenames.map {\n                themesURL.appending(path: $0)\n            }\n\n            // get all bundled theme URLs\n            let bundledThemeFilenames = try filemanager.contentsOfDirectory(atPath: bundledThemesURL.path).filter {\n                $0.contains(\".cetheme\")\n            }\n            let bundledThemeURLs = bundledThemeFilenames.map {\n                bundledThemesURL.appending(path: $0)\n            }\n\n            // combine user theme URLs with bundled theme URLs\n            let themeURLs = userDefinedThemeURLs + bundledThemeURLs\n\n            let prefs = Settings.shared.preferences\n\n            // load each theme from disk and store in memory\n            try themeURLs.forEach { fileURL in\n                if var theme = try load(from: fileURL) {\n\n                    // get all properties of terminal and editor colors\n                    guard let terminalColors = try theme.terminal.allProperties() as? [String: Theme.Attributes],\n                          let editorColors = try theme.editor.allProperties() as? [String: Theme.Attributes]\n                    else {\n                        print(\"error\")\n                        // TODO: Throw a proper error\n                        throw NSError() // swiftlint:disable:this discouraged_direct_init\n                    }\n\n                    // check if there are any overrides in `settings.json`\n                    if let overrides = prefs.theme.overrides[theme.name]?[\"terminal\"] {\n                        terminalColors.forEach { (key, _) in\n                            if let attributes = overrides[key] {\n                                theme.terminal[key] = attributes\n                            }\n                        }\n                    }\n\n                    if let overrides = prefs.theme.overrides[theme.name]?[\"editor\"] {\n                        editorColors.forEach { (key, _) in\n                            if let attributes = overrides[key] {\n                                theme.editor[key] = attributes\n                            }\n                        }\n                    }\n\n                    theme.isBundled = fileURL.path.contains(bundledThemesURL.path)\n\n                    theme.fileURL = fileURL\n\n                    // add the theme to themes array\n                    self.themes.append(theme)\n\n                    // if there already is a selected theme in `settings.json` select this theme\n                    // otherwise take the first in the list\n                    self.selectedDarkTheme = self.darkThemes.first {\n                        $0.name == prefs.theme.selectedDarkTheme\n                    } ?? self.darkThemes.first\n\n                    self.selectedLightTheme = self.lightThemes.first {\n                        $0.name == prefs.theme.selectedLightTheme\n                    } ?? self.lightThemes.first\n\n                    // For selecting the default theme, doing it correctly on startup requires some more logic\n                    let userSelectedTheme = self.themes.first { $0.name == prefs.theme.selectedTheme }\n                    let systemAppearance = NSAppearance.currentDrawing().name\n\n                    if userSelectedTheme != nil {\n                        self.selectedTheme = userSelectedTheme\n                    } else {\n                        if systemAppearance == .darkAqua {\n                            self.selectedTheme = self.selectedDarkTheme\n                        } else {\n                            self.selectedTheme = self.selectedLightTheme\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    func importTheme() {\n        let openPanel = NSOpenPanel()\n        let allowedTypes = [UTType(filenameExtension: \"cetheme\")!]\n\n        openPanel.prompt = \"Import\"\n        openPanel.allowedContentTypes = allowedTypes\n        openPanel.canChooseFiles = true\n        openPanel.canChooseDirectories = false\n        openPanel.allowsMultipleSelection = false\n\n        openPanel.begin { result in\n            if result.rawValue == NSApplication.ModalResponse.OK.rawValue {\n                if let url = openPanel.urls.first {\n                    self.duplicate(url)\n                }\n            }\n        }\n    }\n\n    func duplicate(_ url: URL) {\n        do {\n            self.isAdding = true\n            // Construct the destination file URL\n            var destinationFileURL = self.themesURL.appending(path: url.lastPathComponent)\n\n            // Extract the base filename and extension\n            let fileExtension = destinationFileURL.pathExtension\n\n            var fileName = destinationFileURL.deletingPathExtension().lastPathComponent\n            var newFileName = fileName\n\n            var iterator = 1\n\n            let isBundled = url.absoluteString.hasPrefix(bundledThemesURL?.absoluteString ?? \"\")\n            let isImporting =\n                !url.absoluteString.hasPrefix(bundledThemesURL?.absoluteString ?? \"\")\n                && !url.absoluteString.hasPrefix(themesURL.absoluteString)\n\n            if isBundled {\n                newFileName = \"\\(fileName) \\(iterator)\"\n                destinationFileURL = self.themesURL\n                    .appending(path: newFileName)\n                    .appendingPathExtension(fileExtension)\n            }\n\n            // Check if the file already exists\n            while FileManager.default.fileExists(atPath: destinationFileURL.path) {\n                fileName = destinationFileURL.deletingPathExtension().lastPathComponent\n\n                // Remove any existing iterator\n                if let range = fileName.range(of: \" \\\\d+$\", options: .regularExpression) {\n                    fileName = String(fileName[..<range.lowerBound])\n                }\n\n                // Generate a new filename with an iterator\n                newFileName = \"\\(fileName) \\(iterator)\"\n                destinationFileURL = self.themesURL\n                    .appending(path: newFileName)\n                    .appendingPathExtension(fileExtension)\n\n                iterator += 1\n            }\n\n            // Copy the file from selected URL to the destination\n            try FileManager.default.copyItem(at: url, to: destinationFileURL)\n\n            try self.loadThemes()\n\n            if let index = self.themes.firstIndex(where: { $0.fileURL == destinationFileURL }) {\n                self.themes[index].displayName = newFileName\n                self.themes[index].name = newFileName.lowercased().replacingOccurrences(of: \" \", with: \"-\")\n\n                if isImporting != true {\n                    self.themes[index].author = NSFullUserName()\n                    self.save(self.themes[index])\n                }\n\n                self.previousTheme = self.selectedTheme\n\n                activateTheme(self.themes[index])\n\n                self.detailsTheme = self.themes[index]\n                self.detailsIsPresented = true\n            }\n        } catch {\n            print(\"Error adding theme: \\(error.localizedDescription)\")\n        }\n    }\n\n    func rename(to newName: String, theme: Theme) {\n        do {\n            guard let oldURL = theme.fileURL else {\n                throw NSError(\n                    domain: \"ThemeModel\",\n                    code: 1,\n                    userInfo: [NSLocalizedDescriptionKey: \"Theme file URL not found\"]\n                )\n            }\n\n            var finalName = newName\n            var finalURL = themesURL.appending(path: finalName).appendingPathExtension(\"cetheme\")\n            var iterator = 1\n\n            // Check for existing display names in themes\n            while themes.contains(where: { theme != $0 && $0.displayName == finalName }) {\n                finalName = \"\\(newName) \\(iterator)\"\n                finalURL = themesURL.appending(path: finalName).appendingPathExtension(\"cetheme\")\n                iterator += 1\n            }\n\n            _ = self.getThemeActive(theme)\n\n            try filemanager.moveItem(at: oldURL, to: finalURL)\n\n            try self.loadThemes()\n        } catch {\n            print(\"Error renaming theme: \\(error.localizedDescription)\")\n        }\n    }\n\n    /// Save theme to file\n    func save(_ theme: Theme) {\n        do {\n            if let fileURL = theme.fileURL {\n                let encoder = JSONEncoder()\n                encoder.outputFormatting = [.sortedKeys]\n                let data = try encoder.encode(theme)\n                let json = try JSONSerialization.jsonObject(with: data)\n                let prettyJSON = try JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted])\n                try prettyJSON.write(to: fileURL, options: .atomic)\n            }\n        } catch {\n            print(\"Error saving theme: \\(error.localizedDescription)\")\n        }\n    }\n\n    /// Removes the given theme from `–/Library/Application Support/CodeEdit/themes`\n    ///\n    /// After removing the theme, themes are reloaded\n    /// from `~/Library/Application Support/CodeEdit/Themes`. See ``loadThemes()``\n    /// for more information.\n    ///\n    /// - Parameter theme: The theme to delete\n    func delete(_ theme: Theme) {\n        if let url = theme.fileURL {\n            do {\n                try filemanager.removeItem(at: url)\n\n                Settings.shared.preferences.theme.overrides.removeValue(forKey: theme.name)\n\n                try self.loadThemes()\n            } catch {\n                print(error)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/ThemeSettings/Models/ThemeModel.swift",
    "content": "//\n//  ThemeModel.swift\n//  CodeEditModules/Settings\n//\n//  Created by Lukas Pistrol on 31.03.22.\n//\n\nimport SwiftUI\nimport UniformTypeIdentifiers\n\n/// The Theme View Model. Accessible via the singleton \"``ThemeModel/shared``\".\n///\n/// **Usage:**\n/// ```swift\n/// @StateObject\n/// private var themeModel: ThemeModel = .shared\n/// ```\nfinal class ThemeModel: ObservableObject {\n    static let shared: ThemeModel = .init()\n\n    @AppSettings(\\.theme)\n    var settings\n\n    /// Default instance of the `FileManager`\n    let filemanager = FileManager.default\n\n    /// The base folder url `~/Library/Application Support/CodeEdit/`\n    private var baseURL: URL {\n        filemanager.homeDirectoryForCurrentUser.appending(path: \"Library/Application Support/CodeEdit\")\n    }\n\n    var bundledThemesURL: URL? {\n        Bundle.main.resourceURL?.appending(path: \"DefaultThemes\", directoryHint: .isDirectory) ?? nil\n    }\n\n    /// The URL of the `Themes` folder\n    internal var themesURL: URL {\n        baseURL.appending(path: \"Themes\", directoryHint: .isDirectory)\n    }\n\n    /// The URL of the `Extensions` folder\n    internal var extensionsURL: URL {\n        baseURL.appending(path: \"Extensions\", directoryHint: .isDirectory)\n    }\n\n    /// The URL of the `settings.json` file\n    internal var settingsURL: URL {\n        baseURL.appending(path: \"settings.json\", directoryHint: .isDirectory)\n    }\n\n    /// System color scheme\n    @Published var colorScheme: ColorScheme = .light\n\n    /// Selected 'light' theme\n    /// Used for auto-switching theme to match macOS system appearance\n    @Published var selectedLightTheme: Theme? {\n        didSet {\n            DispatchQueue.main.async {\n                Settings.shared\n                    .preferences.theme.selectedLightTheme = self.selectedLightTheme?.name ?? \"Broken\"\n            }\n        }\n    }\n\n    /// Selected 'dark' theme\n    /// Used for auto-switching theme to match macOS system appearance\n    @Published var selectedDarkTheme: Theme? {\n        didSet {\n            DispatchQueue.main.async {\n                Settings.shared\n                    .preferences.theme.selectedDarkTheme = self.selectedDarkTheme?.name ?? \"Broken\"\n            }\n        }\n    }\n\n    @Published var detailsIsPresented: Bool = false\n\n    @Published var isAdding: Bool = false\n\n    @Published var detailsTheme: Theme?\n\n    /// An array of loaded ``Theme``.\n    @Published var themes: [Theme] = []\n\n    /// The currently selected ``Theme``.\n    @Published var selectedTheme: Theme? {\n        didSet {\n            DispatchQueue.main.async {\n                Settings[\\.theme].selectedTheme = self.selectedTheme?.name\n            }\n        }\n    }\n\n    @Published var previousTheme: Theme?\n\n    /// Only themes where ``Theme/appearance`` == ``Theme/ThemeType/dark``\n    var darkThemes: [Theme] {\n        themes.filter { $0.appearance == .dark }\n    }\n\n    /// Only themes where ``Theme/appearance`` == ``Theme/ThemeType/light``\n    var lightThemes: [Theme] {\n        themes.filter { $0.appearance == .light }\n    }\n\n    private init() {\n        do {\n            try loadThemes()\n        } catch {\n            print(error)\n        }\n    }\n\n    /// This function stores  'dark' and 'light' themes into `ThemePreferences` if user happens to select a theme\n    func updateAppearanceTheme() {\n        if self.selectedTheme?.appearance == .dark {\n            self.selectedDarkTheme = self.selectedTheme\n        } else if self.selectedTheme?.appearance == .light {\n            self.selectedLightTheme = self.selectedTheme\n        }\n    }\n\n    func cancelDetails(_ theme: Theme) {\n        if let index = themes.firstIndex(where: { $0.fileURL == theme.fileURL }),\n        let detailsTheme = self.detailsTheme {\n            self.themes[index] = detailsTheme\n            self.save(self.themes[index])\n        }\n    }\n\n    /// Initialize to the app's current appearance.\n    var selectedAppearance: ThemeSettingsAppearances {\n        NSApp.effectiveAppearance.name == .darkAqua ? .dark : .light\n    }\n\n    enum ThemeSettingsAppearances: String, CaseIterable {\n        case light = \"Light Appearance\"\n        case dark = \"Dark Appearance\"\n    }\n\n    func getThemeActive(_ theme: Theme) -> Bool {\n        return selectedTheme == theme\n    }\n\n    /// Activates the current theme, setting ``selectedTheme`` and ``selectedLightTheme``/``selectedDarkTheme`` as\n    /// necessary.\n    /// - Parameter theme: The theme to activate.\n    func activateTheme(_ theme: Theme) {\n        selectedTheme = theme\n        if colorScheme == .light {\n            selectedLightTheme = theme\n        }\n        if colorScheme == .dark {\n            selectedDarkTheme = theme\n        }\n    }\n\n    func exportTheme(_ theme: Theme) {\n        guard let themeFileURL = theme.fileURL else {\n            print(\"Theme file URL not found.\")\n            return\n        }\n\n        let savePanel = NSSavePanel()\n        savePanel.allowedContentTypes = [UTType(filenameExtension: \"cetheme\")!]\n        savePanel.nameFieldStringValue = theme.displayName\n        savePanel.prompt = \"Export\"\n        savePanel.canCreateDirectories = true\n\n        savePanel.begin { response in\n            if response == .OK, let destinationURL = savePanel.url {\n                do {\n                    try FileManager.default.copyItem(at: themeFileURL, to: destinationURL)\n                    print(\"Theme exported successfully to \\(destinationURL.path)\")\n                } catch {\n                    print(\"Failed to export theme: \\(error.localizedDescription)\")\n                }\n            }\n        }\n    }\n\n    func exportAllCustomThemes() {\n            let openPanel = NSOpenPanel()\n            openPanel.prompt = \"Export\"\n            openPanel.canChooseFiles = false\n            openPanel.canChooseDirectories = true\n            openPanel.allowsMultipleSelection = false\n\n            openPanel.begin { result in\n                if result == .OK, let exportDirectory = openPanel.url {\n                    let customThemes = self.themes.filter { !$0.isBundled }\n\n                    for theme in customThemes {\n                        guard let sourceURL = theme.fileURL else { continue }\n\n                        let destinationURL = exportDirectory.appending(path: \"\\(theme.displayName).cetheme\")\n\n                        do {\n                            try FileManager.default.copyItem(at: sourceURL, to: destinationURL)\n                            print(\"Exported \\(theme.displayName) to \\(destinationURL.path)\")\n                        } catch {\n                            print(\"Failed to export \\(theme.displayName): \\(error.localizedDescription)\")\n                        }\n                    }\n                }\n            }\n        }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/ThemeSettings/Models/ThemeSettings.swift",
    "content": "//\n//  ThemePreferences.swift\n//  CodeEditModules/Settings\n//\n//  Created by Nanashi Li on 2022/04/08.\n//\n\nimport Foundation\n\nextension SettingsData {\n\n    /// A dictionary containing the keys and associated ``Theme/Attributes`` of overridden properties\n    ///\n    /// ```json\n    /// {\n    ///   \"editor\" : {\n    ///     \"background\" : {\n    ///       \"color\" : \"#123456\"\n    ///     },\n    ///     ...\n    ///   },\n    ///   \"terminal\" : {\n    ///     \"blue\" : {\n    ///       \"color\" : \"#1100FF\"\n    ///     },\n    ///     ...\n    ///   }\n    /// }\n    /// ```\n    typealias ThemeOverrides = [String: [String: Theme.Attributes]]\n\n    /// The global settings for themes\n    struct ThemeSettings: Codable, Hashable, SearchableSettingsPage {\n\n        var searchKeys: [String] {\n            [\n                \"Automatically Change theme based on system appearance\",\n                \"Always use dark terminal appearance\",\n                \"Use theme background\",\n                \"Light Appearance\",\n                \"GitHub Light\",\n                \"Xcode Light\",\n                \"Solarized Light\",\n                \"Solarized Dark\",\n                \"Midnight\",\n                \"Xcode Dark\",\n                \"GitHub Dark\"\n            ]\n            .map { NSLocalizedString($0, comment: \"\") }\n        }\n\n        /// The name of the currently selected dark theme\n        var selectedDarkTheme: String = \"Default (Dark)\"\n\n        /// The name of the currently selected light theme\n        var selectedLightTheme: String = \"Default (Light)\"\n\n        /// The name of the currently selected theme\n        var selectedTheme: String?\n\n        /// Use the system background that matches the appearance setting\n        var useThemeBackground: Bool = true\n\n        /// Automatically change theme based on system appearance\n        var matchAppearance: Bool = true\n\n        /// Dictionary of themes containing overrides\n        ///\n        /// ```json\n        /// {\n        ///   \"overrides\" : {\n        ///     \"DefaultDark\" : {\n        ///       \"editor\" : {\n        ///         \"background\" : {\n        ///           \"color\" : \"#123456\"\n        ///         },\n        ///         ...\n        ///       },\n        ///       \"terminal\" : {\n        ///         \"blue\" : {\n        ///           \"color\" : \"#1100FF\"\n        ///         },\n        ///         ...\n        ///       }\n        ///       ...\n        ///     },\n        ///     ...\n        ///   },\n        ///   ...\n        /// }\n        /// ```\n        var overrides: [String: ThemeOverrides] = [:]\n\n        /// Default initializer\n        init() {}\n\n        /// Explicit decoder init for setting default values when key is not present in `JSON`\n        init(from decoder: Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.selectedDarkTheme = try container.decodeIfPresent(\n                String.self, forKey: .selectedDarkTheme\n            ) ?? selectedDarkTheme\n            self.selectedLightTheme = try container.decodeIfPresent(\n                String.self, forKey: .selectedLightTheme\n            ) ?? selectedLightTheme\n            self.selectedTheme = try container.decodeIfPresent(String.self, forKey: .selectedTheme)\n            self.useThemeBackground = try container.decodeIfPresent(Bool.self, forKey: .useThemeBackground) ?? true\n            self.matchAppearance = try container.decodeIfPresent(\n                Bool.self, forKey: .matchAppearance\n            ) ?? true\n            self.overrides = try container.decodeIfPresent([String: ThemeOverrides].self, forKey: .overrides) ?? [:]\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingThemeRow.swift",
    "content": "//\n//  ThemeSettingThemeRow.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 4/3/23.\n//\n\nimport SwiftUI\n\nstruct ThemeSettingsThemeRow: View {\n    @Binding var theme: Theme\n    var active: Bool\n\n    @ObservedObject private var themeModel: ThemeModel = .shared\n\n    @State private var isHovering = false\n\n    @State private var deleteConfirmationIsPresented = false\n\n    var body: some View {\n        HStack {\n            Image(systemName: \"checkmark\")\n                .opacity(active ? 1 : 0)\n                .font(.system(size: 10.5, weight: .bold))\n            VStack(alignment: .leading) {\n                Text(theme.displayName)\n                Text(theme.author)\n                    .foregroundColor(.secondary)\n                    .font(.footnote)\n            }\n            .frame(maxWidth: .infinity, alignment: .leading)\n            if !active {\n                Button {\n                    themeModel.activateTheme(theme)\n                } label: {\n                    Text(\"Choose\")\n                }\n                .buttonStyle(.bordered)\n                .opacity(isHovering ? 1 : 0)\n            }\n            ThemeSettingsColorPreview(theme)\n            Menu {\n                Button(\"Details...\") {\n                    themeModel.detailsTheme = theme\n                    themeModel.detailsIsPresented = true\n                }\n                Button(\"Duplicate...\") {\n                    if let fileURL = theme.fileURL {\n                        themeModel.duplicate(fileURL)\n                    }\n                }\n                Button(\"Export...\") {\n                    themeModel.exportTheme(theme)\n                }\n                .disabled(theme.isBundled)\n                Divider()\n                Button(\"Delete...\") {\n                    deleteConfirmationIsPresented = true\n                }\n                .disabled(theme.isBundled)\n            } label: {\n                Image(systemName: \"ellipsis.circle\")\n                    .font(.system(size: 16))\n            }\n            .buttonStyle(.icon)\n        }\n        .padding(10)\n        .onHover { hovering in\n            isHovering = hovering\n        }\n        .alert(\n            Text(\"Are you sure you want to delete the theme “\\(theme.displayName)”?\"),\n            isPresented: $deleteConfirmationIsPresented\n        ) {\n            Button(\"Delete Theme\") {\n                themeModel.delete(theme)\n            }\n            Button(\"Cancel\") {\n                deleteConfirmationIsPresented = false\n            }\n        } message: {\n            Text(\"This action cannot be undone.\")\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingsColorPreview.swift",
    "content": "//\n//  ThemeSettingsColorPreview.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 4/3/23.\n//\n\nimport SwiftUI\n\nstruct ThemeSettingsColorPreview: View {\n    var theme: Theme\n\n    @StateObject private var themeModel: ThemeModel = .shared\n\n    @State private var displayName: String\n\n    init(_ theme: Theme) {\n        self.theme = theme\n        self.displayName = theme.displayName\n    }\n\n    var body: some View {\n        HStack(spacing: 5) {\n            ThemeSettingsColorPreviewColor(\n                theme.editor.keywords.swiftColor\n            )\n            ThemeSettingsColorPreviewColor(\n                theme.editor.commands.swiftColor\n            )\n            ThemeSettingsColorPreviewColor(\n                theme.editor.types.swiftColor\n            )\n            ThemeSettingsColorPreviewColor(\n                theme.editor.attributes.swiftColor\n            )\n            ThemeSettingsColorPreviewColor(\n                theme.editor.variables.swiftColor\n            )\n            ThemeSettingsColorPreviewColor(\n                theme.editor.values.swiftColor\n            )\n            ThemeSettingsColorPreviewColor(\n                theme.editor.numbers.swiftColor\n            )\n            ThemeSettingsColorPreviewColor(\n                theme.editor.strings.swiftColor\n            )\n            ThemeSettingsColorPreviewColor(\n                theme.editor.characters.swiftColor\n            )\n            ThemeSettingsColorPreviewColor(\n                theme.editor.comments.swiftColor\n            )\n        }\n        .padding(12)\n        .background(theme.editor.background.swiftColor)\n        .clipShape(Capsule())\n        .overlay {\n            ZStack {\n                Capsule()\n                    .stroke(Color(.black).opacity(0.2), lineWidth: 0.5)\n                    .frame(maxWidth: .infinity, maxHeight: .infinity)\n                Capsule()\n                    .strokeBorder(Color(.white).opacity(0.2), lineWidth: 0.5)\n                    .frame(maxWidth: .infinity, maxHeight: .infinity)\n            }\n        }\n    }\n}\n\nstruct ThemeSettingsColorPreviewColor: View {\n    private var color: Color\n\n    init(_ color: Color) {\n        self.color = color\n    }\n\n    var body: some View {\n        color\n            .frame(width: 5, height: 5)\n            .clipShape(RoundedRectangle(cornerRadius: 5))\n            .overlay {\n                ZStack {\n                    Circle()\n                        .stroke(Color(.black).opacity(0.2), lineWidth: 0.5)\n                        .frame(width: 5, height: 5)\n                    Circle()\n                        .strokeBorder(Color(.white).opacity(0.2), lineWidth: 0.5)\n                        .frame(width: 5, height: 5)\n                }\n            }\n\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingsThemeDetails.swift",
    "content": "//\n//  ThemeSettingsThemeDetails.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 4/3/23.\n//\n\nimport SwiftUI\n\nstruct ThemeSettingsThemeDetails: View {\n    @Environment(\\.dismiss)\n    var dismiss\n\n    @Environment(\\.colorScheme)\n    var colorScheme\n\n    @Binding var theme: Theme\n\n    var originalTheme: Theme\n\n    @StateObject private var themeModel: ThemeModel = .shared\n\n    @State private var duplicatingTheme: Theme?\n\n    @State private var deleteConfirmationIsPresented = false\n\n    var isActive: Bool {\n        themeModel.getThemeActive(theme)\n    }\n\n    init(theme: Binding<Theme>) {\n        _theme = theme\n        originalTheme = theme.wrappedValue\n    }\n\n    var body: some View {\n        VStack(spacing: 0) {\n            Form {\n                Group {\n                    Section {\n                        TextField(\"Name\", text: $theme.displayName)\n                        TextField(\"Author\", text: $theme.author)\n                        Picker(\"Type\", selection: $theme.appearance) {\n                            Text(\"Light\")\n                                .tag(Theme.ThemeType.light)\n                            Text(\"Dark\")\n                                .tag(Theme.ThemeType.dark)\n                        }\n                    }\n                    Section(\"Text\") {\n                        SettingsColorPicker(\n                            \"Text\",\n                            color: $theme.editor.text.swiftColor\n                        )\n                        SettingsColorPicker(\n                            \"Cursor\",\n                            color: $theme.editor.insertionPoint.swiftColor\n                        )\n                        SettingsColorPicker(\n                            \"Invisibles\",\n                            color: $theme.editor.invisibles.swiftColor\n                        )\n                    }\n                    Section(\"Background\") {\n                        SettingsColorPicker(\n                            \"Background\",\n                            color: $theme.editor.background.swiftColor\n                        )\n                        SettingsColorPicker(\n                            \"Current Line\",\n                            color: $theme.editor.lineHighlight.swiftColor\n                        )\n                        SettingsColorPicker(\n                            \"Selection\",\n                            color: $theme.editor.selection.swiftColor\n                        )\n                    }\n                    Section(\"Tokens\") {\n                        VStack(spacing: 0) {\n                            ThemeSettingsThemeToken(\n                                \"Keywords\",\n                                color: $theme.editor.keywords.swiftColor,\n                                bold: $theme.editor.keywords.bold,\n                                italic: $theme.editor.keywords.italic\n                            )\n                            Divider().padding(.horizontal, 10)\n                            ThemeSettingsThemeToken(\n                                \"Commands\",\n                                color: $theme.editor.commands.swiftColor,\n                                bold: $theme.editor.commands.bold,\n                                italic: $theme.editor.commands.italic\n                            )\n                            Divider().padding(.horizontal, 10)\n                            ThemeSettingsThemeToken(\n                                \"Types\",\n                                color: $theme.editor.types.swiftColor,\n                                bold: $theme.editor.types.bold,\n                                italic: $theme.editor.types.italic\n                            )\n                            Divider().padding(.horizontal, 10)\n                            ThemeSettingsThemeToken(\n                                \"Attributes\",\n                                color: $theme.editor.attributes.swiftColor,\n                                bold: $theme.editor.attributes.bold,\n                                italic: $theme.editor.attributes.italic\n                            )\n                            Divider().padding(.horizontal, 10)\n                            ThemeSettingsThemeToken(\n                                \"Variables\",\n                                color: $theme.editor.variables.swiftColor,\n                                bold: $theme.editor.variables.bold,\n                                italic: $theme.editor.variables.italic\n                            )\n                            Divider().padding(.horizontal, 10)\n                            ThemeSettingsThemeToken(\n                                \"Values\",\n                                color: $theme.editor.values.swiftColor,\n                                bold: $theme.editor.values.bold,\n                                italic: $theme.editor.values.italic\n                            )\n                            Divider().padding(.horizontal, 10)\n                            ThemeSettingsThemeToken(\n                                \"Numbers\",\n                                color: $theme.editor.numbers.swiftColor,\n                                bold: $theme.editor.numbers.bold,\n                                italic: $theme.editor.numbers.italic\n                            )\n                            Divider().padding(.horizontal, 10)\n                            ThemeSettingsThemeToken(\n                                \"Strings\",\n                                color: $theme.editor.strings.swiftColor,\n                                bold: $theme.editor.strings.bold,\n                                italic: $theme.editor.strings.italic\n                            )\n                            Divider().padding(.horizontal, 10)\n                            ThemeSettingsThemeToken(\n                                \"Characters\",\n                                color: $theme.editor.characters.swiftColor,\n                                bold: $theme.editor.characters.bold,\n                                italic: $theme.editor.characters.italic\n                            )\n                            Divider().padding(.horizontal, 10)\n                            ThemeSettingsThemeToken(\n                                \"Comments\",\n                                color: $theme.editor.comments.swiftColor,\n                                bold: $theme.editor.comments.bold,\n                                italic: $theme.editor.comments.italic\n                            )\n                        }\n                        .background(theme.editor.background.swiftColor)\n                        .padding(-10)\n                        .colorScheme(\n                            theme.appearance == .dark\n                            ? .dark\n                            : theme.appearance == .light\n                            ? .light : colorScheme\n                        )\n                    }\n                }\n                .disabled(theme.isBundled)\n            }\n            .formStyle(.grouped)\n            Divider()\n            HStack {\n                if theme.isBundled {\n                    HStack {\n                        Image(systemName: \"exclamationmark.triangle.fill\")\n                            .font(.body)\n                            .foregroundStyle(Color.yellow)\n                        Text(\"Duplicate this theme to make changes.\")\n                            .font(.subheadline)\n                            .lineLimit(2)\n                    }\n                    .help(\"Bundled themes must be duplicated to make changes.\")\n                    .accessibilityElement(children: .combine)\n                    .accessibilityLabel(\"Warning: Duplicate this theme to make changes.\")\n                } else if !themeModel.isAdding {\n                    Button(role: .destructive) {\n                        deleteConfirmationIsPresented = true\n                    } label: {\n                        Text(\"Delete...\")\n                            .foregroundStyle(.red)\n                            .frame(minWidth: 56)\n                    }\n                    Button {\n                        if let fileURL = theme.fileURL {\n                            duplicatingTheme = theme\n                            themeModel.duplicate(fileURL)\n                        }\n                    } label: {\n                        Text(\"Duplicate...\")\n                            .frame(minWidth: 56)\n                    }\n                }\n                Spacer()\n                if !themeModel.isAdding && theme.isBundled {\n                    Button {\n                        if let fileURL = theme.fileURL {\n                            duplicatingTheme = theme\n                            themeModel.duplicate(fileURL)\n                        }\n                    } label: {\n                        Text(\"Duplicate\")\n                            .frame(minWidth: 56)\n                    }\n                } else {\n                    Button {\n                        if themeModel.isAdding {\n                            if let previousTheme = themeModel.previousTheme {\n                                themeModel.activateTheme(previousTheme)\n                            }\n                            if let duplicatingWithinDetails = duplicatingTheme {\n                                let duplicateTheme = theme\n                                themeModel.detailsTheme = duplicatingWithinDetails\n                                themeModel.delete(duplicateTheme)\n                            } else {\n                                DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {\n                                    themeModel.delete(theme)\n                                }\n                            }\n                        } else {\n                            themeModel.cancelDetails(theme)\n                        }\n\n                        if duplicatingTheme == nil {\n                            dismiss()\n                        } else {\n                            duplicatingTheme = nil\n                            themeModel.isAdding = false\n                        }\n                    } label: {\n                        Text(\"Cancel\")\n                            .frame(minWidth: 56)\n                    }\n                    .buttonStyle(.bordered)\n                }\n                Button {\n                    if !theme.isBundled {\n                        themeModel.rename(to: theme.displayName, theme: theme)\n                    }\n                    dismiss()\n                } label: {\n                    Text(\"Done\")\n                        .frame(minWidth: 56)\n                }\n                .buttonStyle(.borderedProminent)\n            }\n            .padding()\n        }\n        .constrainHeightToWindow()\n        .alert(\n            Text(\"Are you sure you want to delete the theme “\\(theme.displayName)”?\"),\n            isPresented: $deleteConfirmationIsPresented\n        ) {\n            Button(\"Delete Theme\") {\n                themeModel.delete(theme)\n                dismiss()\n            }\n            Button(\"Cancel\") {\n                deleteConfirmationIsPresented = false\n            }\n        } message: {\n            Text(\"This action cannot be undone.\")\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingsThemeToken.swift",
    "content": "//\n//  ThemeSettingsThemeToken.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 5/14/24.\n//\n\nimport SwiftUI\n\nstruct ThemeSettingsThemeToken: View {\n    var label: String\n\n    @Binding var color: Color\n    @Binding var bold: Bool\n    @Binding var italic: Bool\n\n    @State private var selectedColor: Color\n    @State private var isHovering = false\n\n    init(_ label: String, color: Binding<Color>, bold: Binding<Bool>, italic: Binding<Bool>) {\n        self.label = label\n        self._color = color\n        self._bold = bold\n        self._italic = italic\n        self._selectedColor = State(initialValue: color.wrappedValue)\n    }\n\n    var body: some View {\n        LabeledContent {\n            HStack(spacing: 20) {\n                HStack(spacing: 8) {\n                    Toggle(isOn: $bold) {\n                        Image(systemName: \"bold\")\n                    }\n                    .toggleStyle(.icon)\n                    .help(\"Bold\")\n                    Divider()\n                        .fixedSize()\n                    Toggle(isOn: $italic) {\n                        Image(systemName: \"italic\")\n                    }\n                    .toggleStyle(.icon)\n                    .help(\"Italic\")\n                }\n                .opacity(isHovering || bold || italic ? 1 : 0)\n\n                ColorPicker(selection: $selectedColor, supportsOpacity: false) { }\n                    .labelsHidden()\n            }\n        } label: {\n            Text(label)\n                .font(.system(.body, design: .monospaced))\n                .bold(bold)\n                .italic(italic)\n                .foregroundStyle(color)\n        }\n        .padding(10)\n        .onHover { hovering in\n            isHovering = hovering\n        }\n        .onChange(of: selectedColor) { _, newValue in\n            color = newValue\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingsView.swift",
    "content": "//\n//  ThemePreferencesView.swift\n//  CodeEdit\n//\n//  Created by Lukas Pistrol on 30.03.22.\n//\n\nimport SwiftUI\n\n/// A view that implements the `Theme` preference section\nstruct ThemeSettingsView: View {\n    @Environment(\\.colorScheme)\n    var colorScheme\n    @ObservedObject private var themeModel: ThemeModel = .shared\n    @AppSettings(\\.theme)\n    var settings\n    @AppSettings(\\.terminal.darkAppearance)\n    var useDarkTerminalAppearance\n\n    @State private var listView: Bool = false\n    @State private var themeSearchQuery: String = \"\"\n    @State private var filteredThemes: [Theme] = []\n\n    var body: some View {\n        VStack {\n            SettingsForm {\n                Section {\n                    HStack(spacing: 10) {\n                        SearchField(\"Search\", text: $themeSearchQuery)\n\n                        Button {\n                            // As discussed, the expected behavior is to duplicate the selected theme.\n                            if let selectedTheme = themeModel.selectedTheme {\n                                if let fileURL = selectedTheme.fileURL {\n                                    themeModel.duplicate(fileURL)\n                                }\n                            }\n                        } label: {\n                            Image(systemName: \"plus\")\n                        }\n                        .disabled(themeModel.selectedTheme == nil)\n                        .help(\"Create a new Theme\")\n\n                        MenuWithButtonStyle(systemImage: \"ellipsis\", menu: {\n                            Group {\n                                Button {\n                                    themeModel.importTheme()\n                                } label: {\n                                    Text(\"Import Theme...\")\n                                }\n                                Button {\n                                    themeModel.exportAllCustomThemes()\n                                } label: {\n                                    Text(\"Export All Custom Themes...\")\n                                }\n                                .disabled(themeModel.themes.filter { !$0.isBundled }.isEmpty)\n                            }\n                        })\n                        .padding(.horizontal, 5)\n                        .help(\"Import or Export Custom Themes\")\n                    }\n                }\n                if themeSearchQuery.isEmpty {\n                    Section {\n                        changeThemeOnSystemAppearance\n                        if settings.matchAppearance {\n                            alwaysUseDarkTerminalAppearance\n                        }\n                        useThemeBackground\n                    }\n                }\n                Section {\n                    VStack(spacing: 0) {\n                        ForEach(filteredThemes) { theme in\n                            if let themeIndex = themeModel.themes.firstIndex(of: theme) {\n                                Divider().padding(.horizontal, 10)\n                                ThemeSettingsThemeRow(\n                                    theme: $themeModel.themes[themeIndex],\n                                    active: themeModel.getThemeActive(theme)\n                                ).id(theme)\n                            }\n                        }\n                    }\n                    .padding(-10)\n                } footer: {\n                    HStack {\n                        Spacer()\n                        Button(\"Import...\") {\n                            themeModel.importTheme()\n                        }\n                    }\n                    .padding(.top, 10)\n                }\n                .sheet(isPresented: $themeModel.detailsIsPresented, onDismiss: {\n                    DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {\n                        themeModel.isAdding = false\n                    }\n                }, content: {\n                    if let theme = themeModel.detailsTheme, let index = themeModel.themes.firstIndex(where: {\n                        $0.fileURL?.absoluteString == theme.fileURL?.absoluteString\n                    }) {\n                        ThemeSettingsThemeDetails(theme: Binding(\n                            get: { themeModel.themes[index] },\n                            set: { newValue in\n                                if themeModel.detailsIsPresented {\n                                    themeModel.themes[index] = newValue\n                                    themeModel.save(newValue)\n                                    if settings.selectedTheme == theme.name {\n                                        themeModel.activateTheme(newValue)\n                                    }\n                                }\n                            }\n                        ))\n                    }\n                })\n                .onAppear {\n                    updateFilteredThemes()\n                }\n                .onChange(of: themeSearchQuery) { _, _ in\n                    updateFilteredThemes()\n                }\n                .onChange(of: themeModel.themes) { _, _ in\n                    updateFilteredThemes()\n                }\n                .onChange(of: colorScheme) { _, newColorScheme in\n                    updateFilteredThemes(overrideColorScheme: newColorScheme)\n                }\n            }\n        }\n    }\n\n    /// Sorts themes by `colorScheme` and `themeSearchQuery`.\n    /// Dark mode themes appear before light themes if in dark mode, and vice versa.\n    private func updateFilteredThemes(overrideColorScheme: ColorScheme? = nil) {\n        // This check is necessary because, when calling `updateFilteredThemes` from within the\n        // `onChange` handler that monitors the `colorScheme`, there are cases where the function\n        // is invoked with outdated values of `colorScheme`.\n        let isDarkScheme = overrideColorScheme ?? colorScheme == .dark\n\n        let themes: [Theme] = isDarkScheme\n        ? (themeModel.darkThemes + themeModel.lightThemes)\n        : (themeModel.lightThemes + themeModel.darkThemes)\n\n        Task {\n            filteredThemes = themeSearchQuery.isEmpty ? themes : await filterAndSortThemes(themes)\n        }\n    }\n\n    private func filterAndSortThemes(_ themes: [Theme]) async -> [Theme] {\n        return await themes.fuzzySearch(query: themeSearchQuery).map { $1 }\n    }\n}\n\nprivate extension ThemeSettingsView {\n    private var useThemeBackground: some View {\n        Toggle(\"Use theme background \", isOn: $settings.useThemeBackground)\n    }\n\n    private var alwaysUseDarkTerminalAppearance: some View {\n        Toggle(\"Always use dark terminal appearance\", isOn: $useDarkTerminalAppearance)\n    }\n\n    private var changeThemeOnSystemAppearance: some View {\n        Toggle(\n            \"Automatically change theme based on system appearance\",\n            isOn: $settings.matchAppearance\n        )\n        .onChange(of: settings.matchAppearance) { _, value in\n            if value {\n                if colorScheme == .dark {\n                    themeModel.selectedTheme = themeModel.selectedDarkTheme\n                } else {\n                    themeModel.selectedTheme = themeModel.selectedLightTheme\n                }\n            } else {\n                themeModel.selectedTheme = themeModel.themes.first {\n                    $0.name == settings.selectedTheme\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/SettingsView.swift",
    "content": "//\n//  SettingsView.swift\n//  CodeEdit\n//\n//  Created by Raymond Vleeshouwer on 26/03/23.\n//\n\nimport SwiftUI\n\n/// A struct for settings\nstruct SettingsView: View {\n    @StateObject var model = SettingsViewModel()\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    /// Variables for the selected Page, the current search text and software updater\n    @State private var selectedPage: SettingsPage = Self.pages[0].page\n    @State private var searchText: String = \"\"\n    @State private var showDeveloperSettings: Bool = false\n\n    @Environment(\\.presentationMode)\n    var presentationMode\n\n    static var pages: [PageAndSettings] = [\n        .init(\n            SettingsPage(\n                .general,\n                baseColor: .gray,\n                icon: .system(\"gear\")\n            )\n        ),\n        .init(\n            SettingsPage(\n                .accounts,\n                baseColor: .blue,\n                icon: .system(\"at\")\n            )\n        ),\n        .init(\n            SettingsPage(\n                .navigation,\n                baseColor: .green,\n                icon: .system(\"arrow.triangle.turn.up.right.diamond.fill\")\n            )\n        ),\n        .init(\n            SettingsPage(\n                .theme,\n                baseColor: .pink,\n                icon: .system(\"paintbrush.fill\")\n            )\n        ),\n        .init(\n            SettingsPage(\n                .textEditing,\n                baseColor: .blue,\n                icon: .system(\"pencil.line\")\n            )\n        ),\n        .init(\n            SettingsPage(\n                .terminal,\n                baseColor: .blue,\n                icon: .system(\"terminal.fill\")\n            )\n        ),\n        .init(\n            SettingsPage(\n                .search,\n                baseColor: .blue,\n                icon: .system(\"magnifyingglass\")\n            )\n        ),\n        .init(\n            SettingsPage(\n                .sourceControl,\n                baseColor: .blue,\n                icon: .symbol(\"vault\")\n            )\n        ),\n        .init(\n            SettingsPage(\n                .location,\n                baseColor: .green,\n                icon: .system(\"externaldrive.fill\")\n            )\n        ),\n        .init(\n            SettingsPage(\n                .languageServers,\n                baseColor: Color(hex: \"#6A69DC\"), // Purple\n                icon: .system(\"cube.box.fill\")\n            )\n        ),\n        .init(\n            SettingsPage(\n                .developer,\n                baseColor: .pink,\n                icon: .system(\"bolt\")\n            )\n        ),\n    ]\n\n    @ObservedObject private var settings: Settings = .shared\n\n    let updater: SoftwareUpdater\n\n    /// Searches through an array of pages to check if a page name exists in the array\n    private func resultFound(_ page: SettingsPage, pages: [SettingsPage]) -> SettingsSearchResult {\n        let lowercasedSearchText = searchText.lowercased()\n        var returnedPages: [SettingsPage] = []\n        var foundPage = false\n\n        for item in pages where item.name == page.name {\n            if item.isSetting && item.settingName.lowercased().contains(lowercasedSearchText) {\n                returnedPages.append(item)\n            } else if item.name.rawValue.contains(lowercasedSearchText) && !item.isSetting {\n                foundPage = true\n            }\n        }\n\n        return SettingsSearchResult(pageFound: foundPage, pages: returnedPages)\n    }\n\n    /// Gets search results from a settings page and an array of settings\n    @ViewBuilder\n    private func results(_ page: SettingsPage, _ settings: [SettingsPage]) -> some View {\n        if !searchText.isEmpty {\n            let results: SettingsSearchResult = resultFound(page, pages: settings)\n\n            if !results.pages.isEmpty && !page.isSetting {\n                SettingsPageView(page, searchText: searchText)\n\n                ForEach(results.pages, id: \\.settingName) { setting in\n                    NavigationLink(value: setting) {\n                        setting.settingName.highlightOccurrences(searchText)\n                            .padding(.leading, 22)\n                    }\n                }\n            } else if page.name.rawValue.lowercased().contains(searchText.lowercased()) && !page.isSetting {\n                SettingsPageView(page, searchText: searchText)\n            }\n        } else if !page.isSetting {\n            if page.name == .developer && !showDeveloperSettings {\n                EmptyView()\n            } else {\n                SettingsPageView(page, searchText: searchText)\n            }\n        }\n    }\n\n    var body: some View {\n        NavigationSplitView {\n            /// Remove the extra List workaround; macOS 26's sidebar .searchable now matches System Settings\n            if #unavailable(macOS 26.0) {\n                List { }\n                    .searchable(text: $searchText, placement: .sidebar, prompt: \"Search\")\n                    .scrollDisabled(true)\n                    .frame(height: 30)\n                List(selection: $selectedPage) {\n                    Section {\n                        ForEach(Self.pages) { pageAndSettings in\n                            results(pageAndSettings.page, pageAndSettings.settings)\n                        }\n                    }\n                }\n                .navigationSplitViewColumnWidth(215)\n            } else {\n                List(selection: $selectedPage) {\n                    Section {\n                        ForEach(Self.pages) { pageAndSettings in\n                            results(pageAndSettings.page, pageAndSettings.settings)\n                        }\n                    }\n                }\n                .toolbar(removing: .sidebarToggle)\n                .searchable(text: $searchText, placement: .sidebar, prompt: \"Search\")\n                .navigationSplitViewColumnWidth(215)\n            }\n        } detail: {\n            Group {\n                switch selectedPage.name {\n                case .general:\n                    GeneralSettingsView().environmentObject(updater)\n                case .accounts:\n                    AccountsSettingsView()\n                case .navigation:\n                    NavigationSettingsView()\n                case .theme:\n                    ThemeSettingsView()\n                case .textEditing:\n                    TextEditingSettingsView()\n                case .terminal:\n                    TerminalSettingsView()\n                case .search:\n                    SearchSettingsView()\n                case .sourceControl:\n                    SourceControlSettingsView()\n                case .location:\n                    LocationsSettingsView()\n                case .languageServers:\n                    LanguageServersView()\n                case .developer:\n                    DeveloperSettingsView()\n                default:\n                    Text(\"Implementation Needed\").frame(alignment: .center)\n                }\n            }\n            .navigationSplitViewColumnWidth(500)\n            .onAppear {\n                model.backButtonVisible = false\n            }\n        }\n        .hideSidebarToggle()\n        .navigationTitle(selectedPage.name.rawValue)\n        .toolbar {\n            /// macOS 26 automatically adjusts the leading padding for navigationTitle\n            if #unavailable(macOS 26.0) {\n                ToolbarItem(placement: .navigation) {\n                    if !model.backButtonVisible {\n                        Rectangle()\n                            .frame(width: 10)\n                            .opacity(0)\n                    } else {\n                        EmptyView()\n                    }\n                }\n            }\n        }\n        .environmentObject(model)\n        .onAppear {\n            // Monitor for the F12 key down event to toggle the developer settings\n            model.setKeyDownMonitor { event in\n                if event.keyCode == 111 {\n                    showDeveloperSettings.toggle()\n\n                    // If the developer menu is hidden and is selected, go back to default page\n                    if !showDeveloperSettings && selectedPage.name == .developer {\n                        selectedPage = Self.pages[0].page\n                    }\n                    return nil\n                }\n                return event\n            }\n        }\n        .onDisappear {\n            model.removeKeyDownMonitor()\n        }\n    }\n}\n\nclass SettingsViewModel: ObservableObject {\n    @Published var backButtonVisible: Bool = false\n    @Published var scrolledToTop: Bool = false\n\n    /// Holds a monitor closure for the `keyDown` event\n    private var keyDownEventMonitor: Any?\n\n    func setKeyDownMonitor(monitor: @escaping (NSEvent) -> NSEvent?) {\n        keyDownEventMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: monitor)\n    }\n\n    func removeKeyDownMonitor() {\n        if let eventMonitor = keyDownEventMonitor {\n            NSEvent.removeMonitor(eventMonitor)\n            self.keyDownEventMonitor = nil\n        }\n    }\n\n    deinit {\n        removeKeyDownMonitor()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/SettingsWindow.swift",
    "content": "//\n//  SettingsWindow.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 3/31/23.\n//\n\nimport SwiftUI\n\nstruct SettingsWindow: Scene {\n    private let updater = SoftwareUpdater()\n\n    var body: some Scene {\n        Window(\"Settings\", id: SceneID.settings.rawValue) {\n            SettingsView(updater: updater)\n                .frame(minWidth: 715, maxWidth: 715)\n                .task {\n                    let window = NSApp.windows.first { $0.identifier?.rawValue == SceneID.settings.rawValue }!\n                    window.titlebarAppearsTransparent = true\n                }\n        }\n        .windowStyle(.automatic)\n        .windowToolbarStyle(.unified)\n        .windowResizability(.contentSize)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/SoftwareUpdater.swift",
    "content": "//\n//  SoftwareUpdater.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 9/19/22.\n//\n\nimport Foundation\nimport Sparkle\n\nclass SoftwareUpdater: NSObject, ObservableObject, SPUUpdaterDelegate {\n    private var updater: SPUUpdater?\n    private var automaticallyChecksForUpdatesObservation: NSKeyValueObservation?\n    private var lastUpdateCheckDateObservation: NSKeyValueObservation?\n    private var appcastURL = URL(\n        string: \"https://github.com/CodeEditApp/CodeEdit/releases/download/latest/appcast.xml\"\n    )!\n\n    @Published var automaticallyChecksForUpdates = false {\n        didSet {\n            updater?.automaticallyChecksForUpdates = automaticallyChecksForUpdates\n        }\n    }\n\n    @Published var lastUpdateCheckDate: Date?\n\n    @Published var includePrereleaseVersions = true {\n        didSet {\n            UserDefaults.standard.setValue(includePrereleaseVersions, forKey: \"includePrereleaseVersions\")\n        }\n    }\n\n    private var feedURLTask: Task<(), Never>?\n\n    private func setFeedURL() async {\n        let url = URL(string: \"https://api.github.com/repos/CodeEditApp/CodeEdit/releases/latest\")!\n        let request = URLRequest(url: url)\n        guard let data = try? await URLSession.shared.data(for: request),\n              let result = try? JSONDecoder().decode(GHAPIResult.self, from: data.0) else {\n            await MainActor.run {\n                self.updater?.setFeedURL(nil)\n            }\n            return\n        }\n        await MainActor.run {\n            appcastURL = URL(\n                string: \"https://github.com/CodeEditApp/CodeEdit/releases/download/\\(result.tagName)/appcast.xml\"\n            )!\n            self.updater?.setFeedURL(appcastURL)\n        }\n    }\n\n    override init() {\n        super.init()\n        updater = SPUStandardUpdaterController(\n            startingUpdater: true,\n            updaterDelegate: self,\n            userDriverDelegate: nil\n        ).updater\n\n        feedURLTask = Task {\n            await setFeedURL()\n        }\n\n        automaticallyChecksForUpdatesObservation = updater?.observe(\n            \\.automaticallyChecksForUpdates,\n            options: [.initial, .new, .old],\n            changeHandler: { [unowned self] updater, change in\n                guard change.newValue != change.oldValue else { return }\n                self.automaticallyChecksForUpdates = updater.automaticallyChecksForUpdates\n            }\n        )\n\n        lastUpdateCheckDateObservation = updater?.observe(\n            \\.lastUpdateCheckDate,\n            options: [.initial, .new, .old],\n            changeHandler: { [unowned self] updater, _ in\n                self.lastUpdateCheckDate = updater.lastUpdateCheckDate\n            }\n        )\n\n        includePrereleaseVersions = UserDefaults.standard.bool(forKey: \"includePrereleaseVersions\")\n    }\n\n    deinit {\n        feedURLTask?.cancel()\n    }\n\n    func allowedChannels(for updater: SPUUpdater) -> Set<String> {\n        // TODO: Uncomment when production build is released. \n        // if includePrereleaseVersions {\n        return [\"dev\"]\n        // }\n        // return []\n    }\n\n    func checkForUpdates() {\n        updater?.checkForUpdates()\n    }\n\n    private struct GHAPIResult: Codable {\n        enum CodingKeys: String, CodingKey {\n            case tagName = \"tag_name\"\n        }\n\n        var tagName: String\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Views/ExternalLink.swift",
    "content": "//\n//  ExternalLink.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 4/20/23.\n//\n\nimport SwiftUI\n\n// Usage 1: ExternalLink with title\n//    ExternalLink(\"Title\", destination: URL(string: \"https://apple.com\")!)\n//\n// Usage 2: ExternalLink with title and subtitle but no icon\n//    ExternalLink(destination: URL(string: \"https://apple.com\")!) {\n//        Text(\"Title\")\n//        Text(\"Subtitle\")\n//    } icon: {\n//        Image(systemName: \"star\")\n//    }\n//\n// Usage 3: ExternalLink with title, subtitle and icon\n//    ExternalLink(destination: URL(string: \"https://apple.com\")!) {\n//        Text(\"Title\")\n//        Text(\"Subtitle\")\n//    } icon: {\n//        Image(systemName: \"star\")\n//    }\n\nstruct ExternalLink<Content: View, Icon: View>: View {\n    let title: String?\n    let subtitle: String?\n    let showInFinder: Bool\n    let destination: URL\n    let icon: (() -> Icon)?\n    let content: () -> Content\n\n    init(\n        _ title: String,\n        showInFinder: Bool = false,\n        destination: URL,\n        subtitle: String? = nil,\n        icon: (() -> Icon)? = nil\n    ) where Content == EmptyView, Icon == EmptyView {\n        self.title = title\n        self.showInFinder = showInFinder\n        self.subtitle = subtitle\n        self.destination = destination\n        self.icon = icon\n        self.content = { EmptyView() }\n    }\n\n    init(\n        showInFinder: Bool = false,\n        destination: URL,\n        @ViewBuilder content: @escaping () -> Content,\n        title: String? = nil,\n        subtitle: String? = nil,\n        @ViewBuilder icon: @escaping () -> Icon = { EmptyView() }\n    ) {\n        self.showInFinder = showInFinder\n        self.title = title\n        self.subtitle = subtitle\n        self.destination = destination\n        self.icon = icon\n        self.content = content\n    }\n\n    var body: some View {\n        Button(action: {\n            if showInFinder {\n                NSWorkspace.shared.activateFileViewerSelecting([destination])\n            } else {\n                NSWorkspace.shared.open(destination)\n            }\n        }, label: {\n            HStack(spacing: 8) {\n                icon?()\n                VStack(alignment: .leading, spacing: 2) {\n                    if let title = title {\n                        Text(title)\n                    }\n                    if let subtitle = subtitle {\n                        Text(subtitle)\n                            .font(.caption)\n                            .foregroundColor(Color(.secondaryLabelColor))\n                    }\n                    content()\n                }\n                Spacer()\n                Image(systemName: \"arrow.up.right\")\n                    .font(.system(size: 12, weight: .semibold))\n                    .foregroundColor(Color(.tertiaryLabelColor))\n            }\n        })\n        .buttonStyle(ExternalLinkButtonStyle())\n    }\n}\n\nstruct ExternalLinkButtonStyle: ButtonStyle {\n    func makeBody(configuration: Configuration) -> some View {\n        configuration.label\n            .padding(10)\n            .background(configuration.isPressed ? Color(.separatorColor) : Color(.clear))\n            .contentShape(Rectangle())\n            .padding(-10)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Views/FontWeightPicker.swift",
    "content": "//\n//  FontWeightPicker.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 6/13/24.\n//\n\nimport SwiftUI\n\nstruct FontWeightPicker: View {\n    @Binding var selection: NSFont.Weight\n    var label: String?\n\n    let fontWeights: [NSFont.Weight] = [\n        .ultraLight,\n        .thin,\n        .light,\n        .regular,\n        .medium,\n        .semibold,\n        .bold,\n        .heavy,\n        .black\n    ]\n\n    var weightNames: [NSFont.Weight: String] = [\n        .ultraLight: \"Ultra Light\",\n        .thin: \"Thin\",\n        .light: \"Light\",\n        .regular: \"Regular\",\n        .medium: \"Medium\",\n        .semibold: \"Semi Bold\",\n        .bold: \"Bold\",\n        .heavy: \"Heavy\",\n        .black: \"Black\"\n    ]\n\n    var body: some View {\n        Picker(label ?? \"Font Weight\", selection: $selection) {\n            ForEach(fontWeights, id: \\.self) { weight in\n                Text(weightNames[weight] ?? \"Unknown\")\n                    .tag(weight)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Views/GlobPatternList.swift",
    "content": "//\n//  GlobPatternList.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/2/24.\n//\n\nimport SwiftUI\n\nstruct GlobPatternList: View {\n    @Binding var patterns: [GlobPattern]\n    @Binding var selection: Set<UUID>\n    let addPattern: () -> Void\n    let removePatterns: (_ selection: Set<UUID>?) -> Void\n    let emptyMessage: String\n\n    @FocusState private var focusedField: String?\n\n    var body: some View {\n        List(selection: $selection) {\n            ForEach(Array(patterns.enumerated()), id: \\.element.id) { index, pattern in\n                GlobPatternListItem(\n                    pattern: $patterns[index],\n                    selection: $selection,\n                    addPattern: addPattern,\n                    removePatterns: removePatterns,\n                    focusedField: $focusedField,\n                    isLast: patterns.count == index + 1\n                )\n                .onAppear {\n                    if pattern.value.isEmpty {\n                        focusedField = pattern.id.uuidString\n                    }\n                }\n            }\n            .onMove { fromOffsets, toOffset in\n                patterns.move(fromOffsets: fromOffsets, toOffset: toOffset)\n            }\n            .onDelete { indexSet in\n                let patternIDs = indexSet.compactMap { patterns[$0].id }\n                    removePatterns(Set(patternIDs))\n            }\n        }\n        .frame(minHeight: 96)\n        .contextMenu(forSelectionType: UUID.self, menu: { selection in\n            if let patternID = selection.first, let pattern = patterns.first(where: { $0.id == patternID }) {\n                Button(\"Edit\") {\n                    focusedField = pattern.id.uuidString\n                }\n                Button(\"Add\") {\n                    addPattern()\n                }\n                Divider()\n                Button(\"Remove\") {\n                    removePatterns(selection)\n                }\n            }\n        }, primaryAction: { selection in\n            if let patternID = selection.first, let pattern = patterns.first(where: { $0.id == patternID }) {\n                focusedField = pattern.id.uuidString\n            }\n        })\n        .overlay {\n            if patterns.isEmpty {\n                Text(emptyMessage)\n                    .foregroundStyle(Color(.secondaryLabelColor))\n            }\n        }\n        .actionBar {\n            Button(action: addPattern) {\n                Image(systemName: \"plus\")\n            }\n            Divider()\n            Button {\n                removePatterns(selection)\n            } label: {\n                Image(systemName: \"minus\")\n                    .opacity(selection.isEmpty ? 0.5 : 1)\n            }\n            .disabled(selection.isEmpty)\n        }\n        .onDeleteCommand {\n            removePatterns(selection)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Views/GlobPatternListItem.swift",
    "content": "//\n//  GlobPatternListItem.swift\n//  CodeEdit\n//\n//  Created by Esteban on 2/2/24.\n//\n\nimport SwiftUI\n\nstruct GlobPatternListItem: View {\n    @Binding var pattern: GlobPattern\n    @Binding var selection: Set<UUID>\n    var addPattern: () -> Void\n    var removePatterns: (_ selection: Set<UUID>?) -> Void\n    var focusedField: FocusState<String?>.Binding\n    var isLast: Bool\n\n    @State var value: String\n\n    @FocusState private var isFocused: Bool\n\n    init(\n        pattern: Binding<GlobPattern>,\n        selection: Binding<Set<UUID>>,\n        addPattern: @escaping () -> Void,\n        removePatterns: @escaping (_ selection: Set<UUID>?) -> Void,\n        focusedField: FocusState<String?>.Binding,\n        isLast: Bool\n    ) {\n        self._pattern = pattern\n        self._selection = selection\n        self.addPattern = addPattern\n        self.removePatterns = removePatterns\n        self.focusedField = focusedField\n        self.isLast = isLast\n\n        self._value = State(initialValue: pattern.wrappedValue.value)\n    }\n\n    var body: some View {\n        TextField(\"\", text: $value)\n            .focused(focusedField, equals: pattern.id.uuidString)\n            .focused($isFocused)\n            .disableAutocorrection(true)\n            .autocorrectionDisabled()\n            .labelsHidden()\n            .onSubmit {\n                if !value.isEmpty {\n                    if isLast {\n                        addPattern()\n                    }\n                }\n            }\n            .onChange(of: isFocused) { _, newIsFocused in\n                if newIsFocused {\n                    if !selection.contains(pattern.id) {\n                        selection = [pattern.id]\n                    }\n                } else if value.isEmpty {\n                    removePatterns(selection)\n                } else if pattern.value != value {\n                    pattern.value = value\n                }\n            }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Views/InvisibleCharacterWarningList.swift",
    "content": "//\n//  InvisibleCharacterWarningList.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 6/13/25.\n//\n\nimport SwiftUI\n\nstruct InvisibleCharacterWarningList: View {\n    @Binding var items: [UInt16: String]\n\n    @State private var selection: String?\n\n    var body: some View {\n        KeyValueTable(\n            items: Binding(\n                get: {\n                    items.reduce(into: [String: String]()) { dict, keyVal in\n                        let hex = String(keyVal.key, radix: 16).uppercased()\n                        let padding = String(repeating: \"0\", count: 4 - hex.count)\n                        dict[\"U+\" + padding + hex] = keyVal.value\n                    }\n                },\n                set: { dict in\n                    items = dict.reduce(into: [UInt16: String]()) { dict, keyVal in\n                        guard let intFromHex = UInt(hexString: String(keyVal.key.trimmingPrefix(\"U+\"))),\n                              intFromHex < UInt16.max else {\n                            return\n                        }\n                        let charCode = UInt16(intFromHex)\n                        dict[charCode] = keyVal.value\n                    }\n                }\n            ),\n            keyColumnName: \"Unicode Character Code\",\n            valueColumnName: \"Notes\",\n            newItemInstruction: \"Add A Character As A Hexidecimal Unicode Value\",\n            actionBarTrailing: {\n                Button {\n                    // Add defaults without removing user's data. We do still override notes here.\n                    items = items.merging(\n                        SettingsData.TextEditingSettings.WarningCharacters.default.characters,\n                        uniquingKeysWith: { _, defaults in\n                            defaults\n                        }\n                    )\n                } label: {\n                    Text(\"Restore Defaults\")\n                }\n                .buttonStyle(PlainButtonStyle())\n                .font(.system(size: 11, weight: .medium))\n                .foregroundStyle(.secondary)\n                .fixedSize(horizontal: true, vertical: false)\n                .padding(.trailing, 4)\n            }\n        )\n        .frame(minHeight: 96, maxHeight: .infinity)\n        .overlay {\n            if items.isEmpty {\n                Text(\"No warning characters\")\n                    .foregroundStyle(Color(.secondaryLabelColor))\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Views/MonospacedFontPicker.swift",
    "content": "//\n//  MonospacedFontPicker.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 4/22/23.\n//\n\nimport SwiftUI\n\nstruct MonospacedFontPicker: View {\n    var title: String\n    @Binding var selectedFontName: String\n    @State private var recentFonts: [String]\n    @State private var monospacedFontFamilyNames: [String] = []\n    @State private var otherFontFamilyNames: [String] = []\n\n    init(title: String, selectedFontName: Binding<String>) {\n        self.title = title\n        self._selectedFontName = selectedFontName\n        self.recentFonts = UserDefaults.standard.stringArray(forKey: \"recentFonts\") ?? []\n    }\n\n    var body: some View {\n        Picker(selection: $selectedFontName, label: Text(title)) {\n            Text(\"System Font\")\n                .font(Font(NSFont.monospacedSystemFont(ofSize: 13.5, weight: .medium)))\n                .tag(\"SF Mono\")\n\n            if !recentFonts.isEmpty {\n                Divider()\n                ForEach(recentFonts, id: \\.self) { fontFamilyName in\n                    Text(fontFamilyName)\n                        .font(.custom(fontFamilyName, size: 13.5))\n                        .tag(fontFamilyName) // to prevent picker invalid and does not have an associated tag error.\n                }\n            }\n\n            if !monospacedFontFamilyNames.isEmpty {\n                Divider()\n                ForEach(monospacedFontFamilyNames, id: \\.self) { fontFamilyName in\n                    Text(fontFamilyName)\n                        .font(.custom(fontFamilyName, size: 13.5))\n                        .tag(fontFamilyName)\n                }\n            }\n\n            if !otherFontFamilyNames.isEmpty {\n                Divider()\n                Menu {\n                    ForEach(otherFontFamilyNames, id: \\.self) { fontFamilyName in\n                        Button {\n                            pushIntoRecentFonts(fontFamilyName)\n                            selectedFontName = fontFamilyName\n                        } label: {\n                            Text(fontFamilyName)\n                                .font(.custom(fontFamilyName, size: 13.5))\n                        }\n                        .tag(fontFamilyName)\n                    }\n                } label: {\n                    Text(\"Other fonts...\")\n                }\n            }\n        }\n        .onChange(of: selectedFontName) { _, _ in\n            if selectedFontName != \"SF Mono\" {\n                pushIntoRecentFonts(selectedFontName)\n\n                // remove the font to prevent ForEach conflict\n                monospacedFontFamilyNames.removeAll { $0 == selectedFontName }\n                otherFontFamilyNames.removeAll { $0 == selectedFontName }\n            }\n        }\n        .task {\n            await getFonts()\n        }\n    }\n}\n\nextension MonospacedFontPicker {\n    private func pushIntoRecentFonts(_ newItem: String) {\n        recentFonts.removeAll(where: { $0 == newItem })\n        recentFonts.insert(newItem, at: 0)\n        if recentFonts.count > 3 {\n            recentFonts.removeLast()\n        }\n        UserDefaults.standard.set(recentFonts, forKey: \"recentFonts\")\n    }\n\n    private func getFonts() async {\n        await withTaskGroup(of: Void.self) { group in\n            group.addTask {\n                let monospacedFontFamilyNames = await getMonospacedFamilyNames()\n                await MainActor.run {\n                    self.monospacedFontFamilyNames = monospacedFontFamilyNames\n                }\n            }\n\n            group.addTask {\n                let otherFontFamilyNames = await getOtherFontFamilyNames()\n                await MainActor.run {\n                    self.otherFontFamilyNames = otherFontFamilyNames\n                }\n            }\n        }\n    }\n\n    private func getMonospacedFamilyNames() -> [String] {\n        let availableFontFamilies = NSFontManager.shared.availableFontFamilies\n\n        return availableFontFamilies.filter { fontFamilyName in\n            // exclude the font if it is in recentFonts to prevent ForEach conflict\n            if recentFonts.contains(fontFamilyName) {\n               return false\n            }\n\n            // exclude default font\n            if fontFamilyName == \"SF Mono\" {\n               return false\n            }\n\n            // include the font which is fixedPitch\n            // include the font which numberOfGlyphs is greater than 26\n            if let font = NSFont(name: fontFamilyName, size: 14) {\n               return font.isFixedPitch && font.numberOfGlyphs > 26\n            } else {\n               return false\n            }\n        }\n   }\n\n   private func getOtherFontFamilyNames() -> [String] {\n       let availableFontFamilies = NSFontManager.shared.availableFontFamilies\n\n       return availableFontFamilies.filter { fontFamilyName in\n           // exclude the font if it is in recentFonts to prevent ForEach conflict\n           if recentFonts.contains(fontFamilyName) {\n               return false\n           }\n\n           // include the font which is NOT fixedPitch\n           // include the font which numberOfGlyphs is greater than 26\n           if let font = NSFont(name: fontFamilyName, size: 14) {\n               return !font.isFixedPitch && font.numberOfGlyphs > 26\n           } else {\n               return false\n           }\n       }\n   }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Views/SettingsColorPicker.swift",
    "content": "//\n//  SettingsColorPicker.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 4/3/23.\n//\n\nimport SwiftUI\n\nstruct SettingsColorPicker<Content>: View where Content: View {\n\n    /// Color modified elsewhere in user theme\n    @Binding var color: Color\n\n    /// Component private color to display\n    /// UI changes\n    @State private var selectedColor: Color\n\n    private let label: String\n    private let content: Content?\n\n    init(_ label: String, color: Binding<Color>, @ViewBuilder content: @escaping () -> Content) {\n        self._color = color\n        self.label = label\n        self._selectedColor = State(initialValue: color.wrappedValue)\n        self.content = content()\n    }\n\n    init(_ label: String, color: Binding<Color>) where Content == EmptyView {\n        self.init(label, color: color) {\n            EmptyView()\n        }\n    }\n\n    var body: some View {\n        LabeledContent(label) {\n            HStack(spacing: 16) {\n                content\n                ColorPicker(selection: $selectedColor, supportsOpacity: false) { }\n                    .labelsHidden()\n            }\n        }\n        .onChange(of: selectedColor) { _, newValue in\n            color = newValue\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Views/SettingsForm.swift",
    "content": "//\n//  SettingsForm.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 4/8/23.\n//\n\nimport SwiftUI\nimport SwiftUIIntrospect\n\nstruct SettingsForm<Content: View>: View {\n    @Environment(\\.colorScheme)\n    private var colorScheme\n    @Environment(\\.controlActiveState)\n    private var activeState\n    @EnvironmentObject var model: SettingsViewModel\n    @ViewBuilder var content: Content\n\n    var body: some View {\n        NavigationStack {\n            Form {\n                Section {\n                    EmptyView()\n                } footer: {\n                    Rectangle()\n                        .frame(height: 0)\n                        .background(\n                            GeometryReader {\n                                Color.clear.preference(\n                                    key: ViewOffsetKey.self,\n                                    value: -$0.frame(in: .named(\"scroll\")).origin.y\n                                )\n                            }\n                        )\n                        .onPreferenceChange(ViewOffsetKey.self) {\n                            if $0 <= -20.0 && !model.scrolledToTop {\n                                withAnimation {\n                                    model.scrolledToTop = true\n                                }\n                            } else if $0 > -20.0 && model.scrolledToTop {\n                                withAnimation {\n                                    model.scrolledToTop = false\n                                }\n                            }\n                        }\n                }\n                content\n            }\n            .introspect(.scrollView, on: .macOS(.v10_15, .v11, .v12, .v13, .v14, .v15)) {\n                $0.scrollerInsets.top = 50\n            }\n            .formStyle(.grouped)\n            .coordinateSpace(name: \"scroll\")\n        }\n        .safeAreaInset(edge: .top, spacing: -50) {\n            EffectView(.menu)\n                .opacity(!model.scrolledToTop ? 1 : 0)\n                .transaction { transaction in\n                    transaction.animation = nil\n                }\n                .overlay(alignment: .bottom) {\n                    LinearGradient(\n                        gradient: Gradient(\n                            colors: [.black.opacity(colorScheme == .dark ? 1 : 0.17), .black.opacity(0)]\n                        ),\n                        startPoint: .top,\n                        endPoint: .bottom\n                    )\n                    .frame(height: colorScheme == .dark || activeState == .inactive ? 1 : 2)\n                    .padding(.bottom, colorScheme == .dark || activeState == .inactive ? -1 : -2)\n                    .opacity(!model.scrolledToTop ? 1 : 0)\n                    .transition(.opacity)\n                }\n                .ignoresSafeArea()\n                .frame(height: 0)\n        }\n    }\n}\n\nstruct ViewOffsetKey: PreferenceKey {\n    typealias Value = CGFloat\n    static var defaultValue = CGFloat.zero\n    static func reduce(value: inout Value, nextValue: () -> Value) {\n        value += nextValue()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Views/SettingsPageView.swift",
    "content": "//\n//  SettingPageView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 3/31/23.\n//\n\nimport SwiftUI\n\nstruct SettingsPageView: View {\n    var page: SettingsPage\n    var searchText: String\n\n    init(_ page: SettingsPage, searchText: String) {\n        self.page = page\n        self.searchText = searchText\n    }\n\n    private var iconName: String {\n        switch page.icon {\n        case .system(let name), .symbol(let name):\n            return name\n        case .asset(let name):\n            return name\n        case .none:\n            return \"questionmark.circle\" // fallback icon\n        }\n    }\n\n    var body: some View {\n        NavigationLink(value: page) {\n            Label {\n                page.name.rawValue.highlightOccurrences(self.searchText)\n                    .padding(.leading, 2)\n            } icon: {\n                if case .asset(let name) = page.icon {\n                    FeatureIcon(image: Image(name), size: 20)\n                } else {\n                    FeatureIcon(symbol: iconName, color: page.baseColor, size: 20)\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Views/View+ConstrainHeightToWindow.swift",
    "content": "//\n//  View+ConstrainHeightToWindow.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 4/3/23.\n//\n\nimport SwiftUI\n\nextension NSWindow {\n    var isSettingsWindow: Bool {\n        self.identifier?.rawValue == SceneID.settings.rawValue\n    }\n}\n\nextension NSApplication {\n    var settingsWindow: NSWindow? {\n        NSApp.windows.first { $0.isSettingsWindow }\n    }\n}\n\nextension View {\n    func constrainHeightToWindow() -> some View {\n        modifier(ConstrainHeightToWindowViewModifier())\n    }\n}\n\nstruct ConstrainHeightToWindowViewModifier: ViewModifier {\n    @State var height: CGFloat = 100\n\n    func body(content: Content) -> some View {\n        content\n            .frame(height: height-100)\n            .onReceive(NSApp.settingsWindow!.publisher(for: \\.frame)) { newValue in\n                height = newValue.height\n            }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Views/View+HideSidebarToggle.swift",
    "content": "//\n//  View+HideSidebarToggle.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 4/5/23.\n//\n\nimport SwiftUI\nimport SwiftUIIntrospect\n\nextension View {\n    func hideSidebarToggle() -> some View {\n        modifier(HideSidebarToggleViewModifier())\n    }\n}\n\nstruct HideSidebarToggleViewModifier: ViewModifier {\n    func body(content: Content) -> some View {\n        content\n            .introspect(.window, on: .macOS(.v13, .v14, .v15)) { window in\n                if let toolbar = window.toolbar {\n                    let sidebarItem = \"com.apple.SwiftUI.navigationSplitView.toggleSidebar\"\n                    let sidebarToggle = toolbar.items.first(where: { $0.itemIdentifier.rawValue == sidebarItem })\n                    sidebarToggle?.view?.isHidden = true\n                }\n            }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Views/View+NavigationBarBackButtonVisible.swift",
    "content": "//\n//  View+NavigationBarBackButtonVisible.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 4/8/23.\n//\n\nimport SwiftUI\n\nstruct NavigationBarBackButtonVisible: ViewModifier {\n    @Environment(\\.presentationMode)\n    var presentationMode\n    @EnvironmentObject var model: SettingsViewModel\n\n    func body(content: Content) -> some View {\n        content\n        .toolbar {\n            ToolbarItem(placement: .navigation) {\n                Button {\n                    self.presentationMode.wrappedValue.dismiss()\n                } label: {\n                    Image(systemName: \"chevron.left\")\n                        .frame(width: 23)\n                }\n            }\n        }\n        .navigationBarBackButtonHidden()\n        .onAppear {\n            model.backButtonVisible = true\n        }\n        .onDisappear {\n            model.backButtonVisible = false\n        }\n    }\n}\n\nextension View {\n    func navigationBarBackButtonVisible() -> some View {\n        modifier(NavigationBarBackButtonVisible())\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Settings/Views/WarningCharactersView.swift",
    "content": "//\n//  WarningCharactersView.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 6/16/25.\n//\n\nimport SwiftUI\n\nstruct WarningCharactersView: View {\n    typealias Config = SettingsData.TextEditingSettings.WarningCharacters\n\n    @Binding var warningCharacters: Config\n\n    @Environment(\\.dismiss)\n    private var dismiss\n\n    var body: some View {\n        VStack(spacing: 0) {\n            Form {\n                Section {\n                    InvisibleCharacterWarningList(items: $warningCharacters.characters)\n                } header: {\n                    Text(\"Warning Characters\")\n                    Text(\n                        \"CodeEdit can help identify invisible or ambiguous characters, such as zero-width spaces,\" +\n                        \" directional quotes, and more. These will appear with a red block highlighting them.\" +\n                        \" You can disable characters or add more here.\"\n                    )\n                }\n            }\n            .formStyle(.grouped)\n            Divider()\n            HStack {\n                Spacer()\n                Button {\n                    dismiss()\n                } label: {\n                    Text(\"Done\")\n                        .frame(minWidth: 56)\n                }\n                .buttonStyle(.borderedProminent)\n            }\n            .padding()\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/Bitbucket/BitBucketAccount+Token.swift",
    "content": "//\n//  BitBucketAccount+Token.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\n// TODO: DOCS (Nanashi Li)\nextension BitBucketAccount {\n\n    func refreshToken(\n        _ session: GitURLSession,\n        oauthConfig: BitBucketOAuthConfiguration,\n        refreshToken: String,\n        completion: @escaping (_ response: Result<BitBucketTokenConfiguration, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let request = BitBucketTokenRouter.refreshToken(oauthConfig, refreshToken).URLRequest\n\n        var task: GitURLSessionDataTaskProtocol?\n\n        if let request {\n            task = session.dataTask(with: request) { data, response, _ in\n\n                guard let response = response as? HTTPURLResponse else { return }\n\n                guard let data else { return }\n                do {\n                    let responseJSON = try? JSONSerialization.jsonObject(with: data, options: .allowFragments)\n                    if let responseJSON = responseJSON as? [String: AnyObject] {\n                        if response.statusCode != 200 {\n                            let errorDescription = responseJSON[\"error_description\"] as? String ?? \"\"\n                            let error = NSError(\n                                domain: \"com.codeedit.models.accounts.bitbucket\",\n                                code: response.statusCode,\n                                userInfo: [NSLocalizedDescriptionKey: errorDescription]\n                            )\n                            completion(Result.failure(error))\n                        } else {\n                            let tokenConfig = BitBucketTokenConfiguration(json: responseJSON)\n                            completion(Result.success(tokenConfig))\n                        }\n                    }\n                }\n            }\n            task?.resume()\n        }\n        return task\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/Bitbucket/BitBucketAccount.swift",
    "content": "//\n//  BitBucketAccount.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\n// TODO: DOCS (Nanashi Li)\n\nstruct BitBucketAccount {\n    let configuration: BitBucketTokenConfiguration\n\n    init(_ config: BitBucketTokenConfiguration = BitBucketTokenConfiguration()) {\n        configuration = config\n    }\n}\n\nextension GitRouter {\n    internal var URLRequest: Foundation.URLRequest? {\n        request()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/Bitbucket/BitBucketOAuthConfiguration.swift",
    "content": "//\n//  BitBucketOAuthConfiguration.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nstruct BitBucketOAuthConfiguration: GitRouterConfiguration {\n    let provider = SourceControlAccount.Provider.bitbucketCloud\n    var apiEndpoint: String?\n    var accessToken: String?\n    let token: String\n    let secret: String\n    let scopes: [String]\n    let webEndpoint: String?\n    let errorDomain = \"com.codeedit.models.accounts.bitbucket\"\n\n    init(\n        _ url: String? = nil,\n        webURL: String? = nil,\n        token: String,\n        secret: String,\n        scopes: [String]\n    ) {\n        apiEndpoint = url ?? provider.apiURL?.absoluteString\n        webEndpoint = webURL ?? provider.baseURL?.absoluteString\n        self.token = token\n        self.secret = secret\n        self.scopes = []\n    }\n\n    func authenticate() -> URL? {\n        BitBucketOAuthRouter.authorize(self).URLRequest?.url\n    }\n\n    fileprivate func basicAuthenticationString() -> String {\n        let clientIDSecretString = [token, secret].joined(separator: \":\")\n        let clientIDSecretData = clientIDSecretString.data(using: String.Encoding.utf8)\n        let base64 = clientIDSecretData?.base64EncodedString(options: Data.Base64EncodingOptions(rawValue: 0))\n        return \"Basic \\(base64 ?? \"\")\"\n    }\n\n    func basicAuthConfig() -> URLSessionConfiguration {\n        let config = URLSessionConfiguration.default\n        config.httpAdditionalHeaders = [\"Authorization\": basicAuthenticationString()]\n        return config\n    }\n\n    func authorize(\n        _ session: GitURLSession,\n        code: String,\n        completion: @escaping (_ config: BitBucketTokenConfiguration) -> Void\n    ) {\n        let request = BitBucketOAuthRouter.accessToken(self, code).URLRequest\n\n        if let request {\n            let task = session.dataTask(with: request) { data, response, _ in\n                if let response = response as? HTTPURLResponse {\n                    if response.statusCode != 200 {\n                        return\n                    } else {\n                        if let config = self.configFromData(data) {\n                            completion(config)\n                        }\n                    }\n                }\n            }\n            task.resume()\n        }\n    }\n\n    private func configFromData(_ data: Data?) -> BitBucketTokenConfiguration? {\n        guard let data else { return nil }\n        do {\n            guard let json = try JSONSerialization.jsonObject(\n                with: data,\n                options: .allowFragments\n            ) as? [String: AnyObject] else {\n                return nil\n            }\n            let config = BitBucketTokenConfiguration(json: json)\n            return config\n        } catch {\n            return nil\n        }\n    }\n\n    func handleOpenURL(\n        _ session: GitURLSession = URLSession.shared,\n        url: URL,\n        completion: @escaping (_ config: BitBucketTokenConfiguration) -> Void\n    ) {\n        let params = url.bitbucketURLParameters()\n\n        if let code = params[\"code\"] {\n            authorize(session, code: code) { config in\n                completion(config)\n            }\n        }\n    }\n\n    func accessTokenFromResponse(_ response: String) -> String? {\n        let accessTokenParam = response.components(separatedBy: \"&\").first\n        if let accessTokenParam {\n            return accessTokenParam.components(separatedBy: \"=\").last\n        }\n        return nil\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/Bitbucket/BitBucketTokenConfiguration.swift",
    "content": "//\n//  BitBucketTokenConfiguration.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nstruct BitBucketTokenConfiguration: GitRouterConfiguration {\n    let provider = SourceControlAccount.Provider.bitbucketCloud\n    var apiEndpoint: String?\n    var accessToken: String?\n    var refreshToken: String?\n    var expirationDate: Date?\n    let errorDomain = \"com.codeedit.models.accounts.bitbucket\"\n\n    init(json: [String: AnyObject], url: String? = nil) {\n        apiEndpoint = url ?? provider.apiURL?.absoluteString\n        accessToken = json[\"access_token\"] as? String\n        refreshToken = json[\"refresh_token\"] as? String\n        let expiresIn = json[\"expires_in\"] as? Int\n        let currentDate = Date()\n        expirationDate = currentDate.addingTimeInterval(TimeInterval(expiresIn ?? 0))\n    }\n\n    init(\n        _ token: String? = nil,\n        refreshToken: String? = nil,\n        expirationDate: Date? = nil,\n        url: String? = nil\n    ) {\n        apiEndpoint = url ?? provider.apiURL?.absoluteString\n        accessToken = token\n        self.expirationDate = expirationDate\n        self.refreshToken = refreshToken\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/Bitbucket/Model/BitBucketRepositories.swift",
    "content": "//\n//  BitBucketRepositories.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\n// TODO: DOCS (Nanashi Li)\nclass BitBucketRepositories: Codable {\n    var id: String\n    var owner: BitBucketUser\n    var name: String?\n    var fullName: String?\n    var isPrivate: Bool\n    var repositoryDescription: String?\n    var gitURL: String?\n    var sshURL: String?\n    var cloneURL: String?\n    var size: Int\n    var scm: String?\n\n    enum CodingKeys: String, CodingKey {\n        case id = \"uuid\"\n        case owner\n        case name\n        case fullName = \"full_name\"\n        case isPrivate = \"is_private\"\n        case repositoryDescription = \"description\"\n        case gitURL = \"git://\"\n        case sshURL = \"ssh://\"\n        case cloneURL = \"https://\"\n        case size\n        case scm\n    }\n}\n\nenum BitbucketPaginatedResponse<T> {\n    case success(values: T, nextParameters: [String: String])\n    case failure(Error)\n}\n\nextension BitBucketAccount {\n\n    func repositories(\n        _ session: GitURLSession = URLSession.shared,\n        userName: String? = nil,\n        nextParameters: [String: String] = [:],\n        completion: @escaping (_ response: BitbucketPaginatedResponse<[BitBucketRepositories]>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = BitBucketRepositoryRouter.readRepositories(configuration, userName, nextParameters)\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: BitBucketRepositories.self\n        ) { repo, error in\n\n            if let error {\n                completion(BitbucketPaginatedResponse.failure(error))\n            } else {\n                if let repo {\n                    completion(BitbucketPaginatedResponse.success(values: [repo], nextParameters: [:]))\n                }\n            }\n        }\n    }\n\n    func repository(\n        _ session: GitURLSession = URLSession.shared,\n        owner: String,\n        name: String,\n        completion: @escaping (_ response: Result<BitBucketRepositories, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = BitBucketRepositoryRouter.readRepository(configuration, owner, name)\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: BitBucketRepositories.self\n        ) { data, error in\n\n            if let error {\n                completion(Result.failure(error))\n            }\n\n            if let data {\n                completion(Result.success(data))\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/Bitbucket/Model/BitBucketUser.swift",
    "content": "//\n//  BitBucketUser.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\nimport SwiftUI\n\n// TODO: DOCS (Nanashi Li)\nclass BitBucketUser: Codable {\n    var id: String?\n    var login: String?\n    var name: String?\n\n    enum CodingKeys: String, CodingKey {\n        case id\n        case login = \"username\"\n        case name = \"display_name\"\n    }\n}\n\nclass BitBucketEmail: Codable {\n    var isPrimary: Bool\n    var isConfirmed: Bool\n    var type: String?\n    var email: String?\n\n    enum CodingKeys: String, CodingKey {\n        case isPrimary = \"is_primary\"\n        case isConfirmed = \"is_confirmed\"\n        case type = \"type\"\n        case email = \"email\"\n    }\n}\n\nextension BitBucketAccount {\n\n    func me(\n        _ session: GitURLSession = URLSession.shared,\n        completion: @escaping (_ response: Result<BitBucketUser, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n\n            let router = BitBucketUserRouter.readAuthenticatedUser(configuration)\n\n            return router.load(\n                session,\n                dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n                expectedResultType: BitBucketUser.self\n            ) { user, error in\n                if let error {\n                    completion(.failure(error))\n                } else {\n                    if let user {\n                        completion(.success(user))\n                    }\n                }\n            }\n        }\n\n    func emails(\n        _ session: GitURLSession = URLSession.shared,\n        completion: @escaping (_ response: Result<BitBucketEmail, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n\n            let router = BitBucketUserRouter.readEmails(configuration)\n\n            return router.load(\n                session,\n                dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n                expectedResultType: BitBucketEmail.self\n            ) { email, error in\n                if let error {\n                    completion(.failure(error))\n                } else {\n                    if let email {\n                        completion(.success(email))\n                    }\n                }\n            }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/Bitbucket/Routers/BitBucketOAuthRouter.swift",
    "content": "//\n//  BitBucketOAuthRouter.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nenum BitBucketOAuthRouter: GitRouter {\n    case authorize(BitBucketOAuthConfiguration)\n    case accessToken(BitBucketOAuthConfiguration, String)\n\n    var configuration: GitRouterConfiguration? {\n        switch self {\n        case .authorize(let config): return config\n        case .accessToken(let config, _): return config\n        }\n    }\n\n    var method: GitHTTPMethod {\n        switch self {\n        case .authorize:\n            return .GET\n        case .accessToken:\n            return .POST\n        }\n    }\n\n    var encoding: GitHTTPEncoding {\n        switch self {\n        case .authorize:\n            return .url\n        case .accessToken:\n            return .form\n        }\n    }\n\n    var path: String {\n        switch self {\n        case .authorize:\n            return \"site/oauth2/authorize\"\n        case .accessToken:\n            return \"site/oauth2/access_token\"\n        }\n    }\n\n    var params: [String: Any] {\n        switch self {\n        case .authorize(let config):\n            return [\"client_id\": config.token, \"response_type\": \"code\"]\n        case .accessToken(_, let code):\n            return [\"code\": code, \"grant_type\": \"authorization_code\"]\n        }\n    }\n\n    var URLRequest: Foundation.URLRequest? {\n        switch self {\n        case .authorize(let config):\n            let url = URL(string: path, relativeTo: URL(string: config.webEndpoint!)!)\n            let components = URLComponents(url: url!, resolvingAgainstBaseURL: true)\n            return request(components!, parameters: params)\n        case .accessToken(let config, _):\n            let url = URL(string: path, relativeTo: URL(string: config.webEndpoint!)!)\n            let components = URLComponents(url: url!, resolvingAgainstBaseURL: true)\n            return request(components!, parameters: params)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/Bitbucket/Routers/BitBucketRepositoryRouter.swift",
    "content": "//\n//  BitBucketRepositoryRouter.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nenum BitBucketRepositoryRouter: GitRouter {\n    case readRepositories(GitRouterConfiguration, String?, [String: String])\n    case readRepository(GitRouterConfiguration, String, String)\n\n    var configuration: GitRouterConfiguration? {\n        switch self {\n        case .readRepositories(let config, _, _): return config\n        case .readRepository(let config, _, _): return config\n        }\n    }\n\n    var method: GitHTTPMethod {\n        .GET\n    }\n\n    var encoding: GitHTTPEncoding {\n        .url\n    }\n\n    var params: [String: Any] {\n        switch self {\n        case .readRepositories(_, let userName, var nextParameters):\n            if userName != nil {\n                return nextParameters as [String: Any]\n            } else {\n                nextParameters[\"role\"] = \"member\"\n                return nextParameters as [String: Any]\n            }\n        case .readRepository:\n            return [:]\n        }\n    }\n\n    var path: String {\n        switch self {\n        case .readRepositories(_, let userName, _):\n            if let userName {\n                return \"repositories/\\(userName)\"\n            } else {\n                return \"repositories\"\n            }\n        case let .readRepository(_, owner, name):\n            return \"repositories/\\(owner)/\\(name)\"\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/Bitbucket/Routers/BitBucketTokenRouter.swift",
    "content": "//\n//  BitBucketTokenRouter.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nenum BitBucketTokenRouter: GitRouter {\n    case refreshToken(BitBucketOAuthConfiguration, String)\n    case emptyToken(BitBucketOAuthConfiguration, String)\n\n    var configuration: GitRouterConfiguration? {\n        switch self {\n        case .refreshToken(let config, _): return config\n        default: return nil\n        }\n    }\n\n    var method: GitHTTPMethod {\n        .POST\n    }\n\n    var encoding: GitHTTPEncoding {\n        .form\n    }\n\n    var params: [String: Any] {\n        switch self {\n        case .refreshToken(_, let token):\n            return [\"refresh_token\": token, \"grant_type\": \"refresh_token\"]\n        default: return [\"\": \"\"]\n        }\n    }\n\n    var path: String {\n        switch self {\n        case .refreshToken:\n            return \"site/oauth2/access_token\"\n        default: return \"\"\n        }\n    }\n\n    var URLRequest: Foundation.URLRequest? {\n        switch self {\n        case .refreshToken(let config, _):\n            let url = URL(string: path, relativeTo: URL(string: config.webEndpoint!)!)\n            let components = URLComponents(url: url!, resolvingAgainstBaseURL: true)\n            return request(components!, parameters: params)\n        default: return nil\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/Bitbucket/Routers/BitBucketUserRouter.swift",
    "content": "//\n//  BitBucketUserRouter.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nenum BitBucketUserRouter: GitRouter {\n    case readAuthenticatedUser(GitRouterConfiguration)\n    case readEmails(GitRouterConfiguration)\n\n    var configuration: GitRouterConfiguration? {\n        switch self {\n        case .readAuthenticatedUser(let config): return config\n        case .readEmails(let config): return config\n        }\n    }\n\n    var method: GitHTTPMethod {\n        .GET\n    }\n\n    var encoding: GitHTTPEncoding {\n        .url\n    }\n\n    var path: String {\n        switch self {\n        case .readAuthenticatedUser:\n            return \"user\"\n        case .readEmails:\n            return \"user/emails\"\n        }\n    }\n\n    var params: [String: Any] {\n        [:]\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitHub/GitHubAccount.swift",
    "content": "//\n//  GitHubAccount.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\n// TODO: DOCS (Nanashi Li)\n\nstruct GitHubAccount {\n    let configuration: GitHubTokenConfiguration\n\n    init(_ config: GitHubTokenConfiguration = GitHubTokenConfiguration()) {\n        configuration = config\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitHub/GitHubConfiguration.swift",
    "content": "//\n//  GitHubConfiguration.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\nstruct GitHubTokenConfiguration: GitRouterConfiguration {\n    let provider = SourceControlAccount.Provider.github\n    var apiEndpoint: String?\n    var accessToken: String?\n    let errorDomain: String? = \"com.codeedit.models.accounts.github\"\n    let authorizationHeader: String? = \"Basic\"\n\n    /// Custom `Accept` header for API previews.\n    ///\n    /// Used for preview support of new APIs, for instance Reaction API.\n    /// see: https://developer.github.com/changes/2016-05-12-reactions-api-preview/\n    private var previewCustomHeaders: [GitHTTPHeader]?\n\n    var customHeaders: [GitHTTPHeader]? {\n        /// More (non-preview) headers can be appended if needed in the future\n        return previewCustomHeaders\n    }\n\n    init(_ token: String? = nil, url: String? = nil, previewHeaders: [GitHubPreviewHeader] = []) {\n        apiEndpoint = url ?? provider.apiURL?.absoluteString\n        accessToken = token?.data(using: .utf8)!.base64EncodedString()\n        previewCustomHeaders = previewHeaders.map { $0.header }\n    }\n}\n\nstruct GitHubOAuthConfiguration: GitRouterConfiguration {\n    let provider = SourceControlAccount.Provider.github\n    var apiEndpoint: String?\n    var accessToken: String?\n    let token: String\n    let secret: String\n    let scopes: [String]\n    let webEndpoint: String?\n    let errorDomain = \"com.codeedit.models.accounts.github\"\n\n    /// Custom `Accept` header for API previews.\n    ///\n    /// Used for preview support of new APIs, for instance Reaction API.\n    /// see: https://developer.github.com/changes/2016-05-12-reactions-api-preview/\n    private var previewCustomHeaders: [GitHTTPHeader]?\n\n    var customHeaders: [GitHTTPHeader]? {\n        /// More (non-preview) headers can be appended if needed in the future\n        return previewCustomHeaders\n    }\n\n    init(\n        _ url: String? = nil,\n        webURL: String? = nil,\n        token: String,\n        secret: String,\n        scopes: [String],\n        previewHeaders: [GitHubPreviewHeader] = []\n    ) {\n        apiEndpoint = url ?? provider.apiURL?.absoluteString\n        webEndpoint = webURL ?? provider.baseURL?.absoluteString\n        self.token = token\n        self.secret = secret\n        self.scopes = scopes\n        previewCustomHeaders = previewHeaders.map { $0.header }\n    }\n\n    func authenticate() -> URL? {\n        GitHubOAuthRouter.authorize(self).URLRequest?.url\n    }\n\n    func authorize(\n        _ session: GitURLSession = URLSession.shared,\n        code: String,\n        completion: @escaping (_ config: GitHubTokenConfiguration) -> Void\n    ) {\n        let request = GitHubOAuthRouter.accessToken(self, code).URLRequest\n        if let request {\n            let task = session.dataTask(with: request) { data, response, _ in\n                if let response = response as? HTTPURLResponse {\n                    if response.statusCode != 200 {\n                        return\n                    } else {\n                        if let data,\n                           let string = String(bytes: data, encoding: .utf8) {\n                            let accessToken = self.accessTokenFromResponse(string)\n                            if let accessToken {\n                                let config = GitHubTokenConfiguration(accessToken, url: self.apiEndpoint ?? \"\")\n                                completion(config)\n                            }\n                        }\n                    }\n                }\n            }\n            task.resume()\n        }\n    }\n\n    func handleOpenURL(\n        _ session: GitURLSession = URLSession.shared,\n        url: URL,\n        completion: @escaping (_ config: GitHubTokenConfiguration) -> Void\n    ) {\n\n        if let code = url.URLParameters[\"code\"] {\n            authorize(session, code: code) { config in\n                completion(config)\n            }\n        }\n    }\n\n    func accessTokenFromResponse(_ response: String) -> String? {\n        let accessTokenParam = response.components(separatedBy: \"&\").first\n        if let accessTokenParam {\n            return accessTokenParam.components(separatedBy: \"=\").last\n        }\n        return nil\n    }\n}\n\nenum GitHubOAuthRouter: GitRouter {\n    case authorize(GitHubOAuthConfiguration)\n    case accessToken(GitHubOAuthConfiguration, String)\n\n    var configuration: GitRouterConfiguration? {\n        switch self {\n        case let .authorize(config): return config\n        case let .accessToken(config, _): return config\n        }\n    }\n\n    var method: GitHTTPMethod {\n        switch self {\n        case .authorize:\n            return .GET\n        case .accessToken:\n            return .POST\n        }\n    }\n\n    var encoding: GitHTTPEncoding {\n        switch self {\n        case .authorize:\n            return .url\n        case .accessToken:\n            return .form\n        }\n    }\n\n    var path: String {\n        switch self {\n        case .authorize:\n            return \"login/oauth/authorize\"\n        case .accessToken:\n            return \"login/oauth/access_token\"\n        }\n    }\n\n    var params: [String: Any] {\n        switch self {\n        case let .authorize(config):\n            let scope = (config.scopes as NSArray).componentsJoined(by: \",\")\n            return [\"scope\": scope, \"client_id\": config.token, \"allow_signup\": \"false\"]\n        case let .accessToken(config, code):\n            return [\"client_id\": config.token, \"client_secret\": config.secret, \"code\": code]\n        }\n    }\n\n    #if canImport(FoundationNetworking)\n    typealias FoundationURLRequestType = FoundationNetworking.URLRequest\n    #else\n    typealias FoundationURLRequestType = Foundation.URLRequest\n    #endif\n\n    var URLRequest: FoundationURLRequestType? {\n        switch self {\n        case let .authorize(config):\n            let url = URL(string: path, relativeTo: URL(string: config.webEndpoint!)!)\n            let components = URLComponents(url: url!, resolvingAgainstBaseURL: true)\n            return request(components!, parameters: params)\n        case let .accessToken(config, _):\n            let url = URL(string: path, relativeTo: URL(string: config.webEndpoint!)!)\n            let components = URLComponents(url: url!, resolvingAgainstBaseURL: true)\n            return request(components!, parameters: params)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitHub/GitHubOpenness.swift",
    "content": "//\n//  GitHubOpenness.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanshi Li on 2022/03/31.\n//\n\nimport Foundation\n\nenum GitHubOpenness: String, Codable {\n    case open\n    case closed\n    case all\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitHub/GitHubPreviewHeader.swift",
    "content": "//\n//  GitHubPreviewHeader.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\n// TODO: DOCS (Nanashi Li)\n\n/// Some APIs provide additional data for new (preview) APIs if a custom header is added to the request.\n///\n/// - Note: Preview APIs are subject to change.\nenum GitHubPreviewHeader {\n    /// The `Reactions` preview header provides reactions in `Comment`s.\n    case reactions\n\n    var header: GitHTTPHeader {\n        switch self {\n        case .reactions:\n            return GitHTTPHeader(headerField: \"Accept\", value: \"application/vnd.github.squirrel-girl-preview\")\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitHub/Model/GitHubAccount+deleteReference.swift",
    "content": "//\n//  GitHubAccount+deleteReference.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\nextension GitHubAccount {\n\n    /**\n     Deletes a reference.\n        - Parameters:\n            - session: GitURLSession, defaults to URLSession.shared()\n            - owner: The user or organization that owns the repositories.\n            - repo: The repository on which the reference needs to be deleted.\n            - ref: The reference to delete.\n            - completion: Callback for the outcome of the deletion.\n     */\n    @discardableResult\n    func deleteReference(\n        _ session: GitURLSession = URLSession.shared,\n        owner: String,\n        repository: String,\n        ref: String,\n        completion: @escaping (_ response: Error?) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitHubRouter.deleteReference(configuration, owner, repository, ref)\n        return router.load(session, completion: completion)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitHub/Model/GitHubComment.swift",
    "content": "//\n//  GitHubComment.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nstruct GitHubComment: Codable {\n    let id: Int\n    let url: URL\n    let htmlURL: URL\n    let body: String\n    let user: GitHubUser\n    let createdAt: Date\n    let updatedAt: Date\n\n    enum CodingKeys: String, CodingKey {\n        case id, url, body, user\n        case htmlURL = \"html_url\"\n        case createdAt = \"created_at\"\n        case updatedAt = \"updated_at\"\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitHub/Model/GitHubFiles.swift",
    "content": "//\n//  GitHubFiles.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\n// TODO: DOCS (Nanashi Li)\n\nclass GitHubFile: Codable {\n    private(set) var id: Int = -1\n    var rawURL: URL?\n    var filename: String?\n    var type: String?\n    var language: String?\n    var size: Int?\n    var content: String?\n\n    enum CodingKeys: String, CodingKey {\n        case rawURL = \"raw_url\"\n        case filename\n        case type\n        case language\n        case size\n        case content\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitHub/Model/GitHubGist.swift",
    "content": "//\n//  GitHubGist.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\nclass GitHubGist: Codable {\n    typealias GitHubFiles = [String: GitHubFile]\n\n    private(set) var id: String?\n    var url: URL?\n    var forksURL: URL?\n    var commitsURL: URL?\n    var gitPushURL: URL?\n    var gitPullURL: URL?\n    var commentsURL: URL?\n    var htmlURL: URL?\n    var files: GitHubFiles\n    var publicGist: Bool?\n    var createdAt: Date?\n    var updatedAt: Date?\n    var description: String?\n    var comments: Int?\n    var user: GitHubUser?\n    var owner: GitHubUser?\n\n    enum CodingKeys: String, CodingKey {\n        case id\n        case url\n        case forksURL = \"forks_url\"\n        case commitsURL = \"commits_url\"\n        case gitPushURL = \"git_pull_url\"\n        case gitPullURL = \"git_push_url\"\n        case commentsURL = \"comments_url\"\n        case htmlURL = \"html_url\"\n        case files\n        case publicGist = \"public\"\n        case createdAt = \"created_at\"\n        case updatedAt = \"updated_at\"\n        case description\n        case comments\n        case user\n        case owner\n    }\n}\n\nextension GitHubAccount {\n\n    /**\n     Fetches the gists of the authenticated user\n     - parameter session: GitURLSession, defaults to URLSession.sharedSession()\n     - parameter page: Current page for gist pagination. `1` by default.\n     - parameter perPage: Number of gists per page. `100` by default.\n     - parameter completion: Callback for the outcome of the fetch.\n     */\n    @discardableResult\n    func myGists(\n        _ session: GitURLSession = URLSession.shared,\n        page: String = \"1\",\n        perPage: String = \"100\",\n        completion: @escaping (_ response: Result<[GitHubGist], Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n\n        let router = GitHubGistRouter.readAuthenticatedGists(configuration, page, perPage)\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: [GitHubGist].self\n        ) { gists, error in\n\n            if let error {\n                completion(.failure(error))\n            } else {\n                if let gists {\n                    completion(.success(gists))\n                }\n            }\n        }\n    }\n\n    /**\n     Fetches the gists of the specified user\n     - parameter session: GitURLSession, defaults to URLSession.sharedSession()\n     - parameter owner: The username who owns the gists.\n     - parameter page: Current page for gist pagination. `1` by default.\n     - parameter perPage: Number of gists per page. `100` by default.\n     - parameter completion: Callback for the outcome of the fetch.\n     */\n    @discardableResult\n    func gists(\n        _ session: GitURLSession = URLSession.shared,\n        owner: String,\n        page: String = \"1\",\n        perPage: String = \"100\",\n        completion: @escaping (_ response: Result<[GitHubGist], Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n\n        let router = GitHubGistRouter.readGists(configuration, owner, page, perPage)\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: [GitHubGist].self\n        ) { gists, error in\n\n            if let error {\n                completion(.failure(error))\n            } else {\n                if let gists {\n                    completion(.success(gists))\n                }\n            }\n        }\n    }\n\n    /**\n     Fetches an gist\n     - parameter session: GitURLSession, defaults to URLSession.sharedSession()\n     - parameter id: The id of the gist.\n     - parameter completion: Callback for the outcome of the fetch.\n     */\n    @discardableResult\n    func gist(\n        _ session: GitURLSession = URLSession.shared,\n        id: String,\n        completion: @escaping (_ response: Result<GitHubGist, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n\n        let router = GitHubGistRouter.readGist(configuration, id)\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: GitHubGist.self\n        ) { gist, error in\n\n            if let error {\n                completion(.failure(error))\n            } else {\n                if let gist {\n                    completion(.success(gist))\n                }\n            }\n        }\n    }\n\n    /**\n     Creates an gist with a single file.\n     - parameter session: GitURLSession, defaults to URLSession.sharedSession()\n     - parameter description: The description of the gist.\n     - parameter filename: The name of the file in the gist.\n     - parameter fileContent: The content of the file in the gist.\n     - parameter publicAccess: The public/private visibility of the gist.\n     - parameter completion: Callback for the gist that is created.\n     */\n    @discardableResult\n    func postGistFile(\n        _ session: GitURLSession = URLSession.shared,\n        description: String,\n        filename: String,\n        fileContent: String,\n        publicAccess: Bool,\n        completion: @escaping (_ response: Result<GitHubGist, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n\n        let router = GitHubGistRouter.postGistFile(configuration, description, filename, fileContent, publicAccess)\n        let decoder = JSONDecoder()\n\n        decoder.dateDecodingStrategy = .formatted(GitTime.rfc3339DateFormatter)\n\n        return router.post(\n            session,\n            decoder: decoder,\n            expectedResultType: GitHubGist.self\n        ) { gist, error in\n\n            if let error {\n                completion(.failure(error))\n            } else {\n                if let gist {\n                    completion(.success(gist))\n                }\n            }\n        }\n    }\n\n    /**\n     Edits an gist with a single file.\n     - parameter session: GitURLSession, defaults to URLSession.sharedSession()\n     - parameter id: The of the gist to update.\n     - parameter description: The description of the gist.\n     - parameter filename: The name of the file in the gist.\n     - parameter fileContent: The content of the file in the gist.\n     - parameter completion: Callback for the gist that is created.\n     */\n    @discardableResult\n    func patchGistFile(\n        _ session: GitURLSession = URLSession.shared,\n        id: String,\n        description: String,\n        filename: String,\n        fileContent: String,\n        completion: @escaping (_ response: Result<GitHubGist, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n\n        let router = GitHubGistRouter.patchGistFile(configuration, id, description, filename, fileContent)\n        let decoder = JSONDecoder()\n\n        decoder.dateDecodingStrategy = .formatted(GitTime.rfc3339DateFormatter)\n\n        return router.post(\n            session,\n            decoder: decoder,\n            expectedResultType: GitHubGist.self\n        ) { gist, error in\n\n            if let error {\n                completion(.failure(error))\n            } else {\n                if let gist {\n                    completion(.success(gist))\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitHub/Model/GitHubIssue.swift",
    "content": "//\n//  GitHubIssue.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanshi Li on 2022/03/31.\n//\n\nimport Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\nclass GitHubIssue: Codable {\n    private(set) var id: Int = -1\n    var url: URL?\n    var repositoryURL: URL?\n    @available(*, deprecated)\n    var labelsURL: URL?\n    var commentsURL: URL?\n    var eventsURL: URL?\n    var htmlURL: URL?\n    var number: Int\n    var state: GitHubOpenness?\n    var title: String?\n    var body: String?\n    var user: GitHubUser?\n    var assignee: GitHubUser?\n    var locked: Bool?\n    var comments: Int?\n    var closedAt: Date?\n    var createdAt: Date?\n    var updatedAt: Date?\n    var closedBy: GitHubUser?\n\n    enum CodingKeys: String, CodingKey {\n        case id\n        case url\n        case repositoryURL = \"repository_url\"\n        case commentsURL = \"comments_url\"\n        case eventsURL = \"events_url\"\n        case htmlURL = \"html_url\"\n        case number\n        case state\n        case title\n        case body\n        case user\n        case assignee\n        case locked\n        case comments\n        case closedAt = \"closed_at\"\n        case createdAt = \"created_at\"\n        case updatedAt = \"updated_at\"\n        case closedBy = \"closed_by\"\n    }\n}\n\nextension GitHubAccount {\n    /**\n     Fetches the issues of the authenticated user\n     - parameter session: GitURLSession, defaults to URLSession.sharedSession()\n     - parameter state: Issue state. Defaults to open if not specified.\n     - parameter page: Current page for issue pagination. `1` by default.\n     - parameter perPage: Number of issues per page. `100` by default.\n     - parameter completion: Callback for the outcome of the fetch.\n     */\n    @discardableResult\n    func myIssues(\n        _ session: GitURLSession = URLSession.shared,\n        state: GitHubOpenness = .open,\n        page: String = \"1\",\n        perPage: String = \"100\",\n        completion: @escaping (_ response: Result<[GitHubIssue], Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitHubIssueRouter.readAuthenticatedIssues(configuration, page, perPage, state)\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: [GitHubIssue].self\n        ) { issues, error in\n\n            if let error {\n                completion(.failure(error))\n            } else {\n                if let issues {\n                    completion(.success(issues))\n                }\n            }\n        }\n    }\n\n    /**\n     Fetches an issue in a repository\n     - parameter session: GitURLSession, defaults to URLSession.sharedSession()\n     - parameter owner: The user or organization that owns the repository.\n     - parameter repository: The name of the repository.\n     - parameter number: The number of the issue.\n     - parameter completion: Callback for the outcome of the fetch.\n     */\n    @discardableResult\n    func issue(\n        _ session: GitURLSession = URLSession.shared,\n        owner: String, repository: String,\n        number: Int,\n        completion: @escaping (_ response: Result<GitHubIssue, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitHubIssueRouter.readIssue(configuration, owner, repository, number)\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: GitHubIssue.self\n        ) { issue, error in\n\n            if let error {\n                completion(.failure(error))\n            } else {\n                if let issue {\n                    completion(.success(issue))\n                }\n            }\n        }\n    }\n\n    /**\n     Fetches all issues in a repository\n     - parameter session: GitURLSession, defaults to URLSession.sharedSession()\n     - parameter owner: The user or organization that owns the repository.\n     - parameter repository: The name of the repository.\n     - parameter state: Issue state. Defaults to open if not specified.\n     - parameter page: Current page for issue pagination. `1` by default.\n     - parameter perPage: Number of issues per page. `100` by default.\n     - parameter completion: Callback for the outcome of the fetch.\n     */\n    @discardableResult\n    func issues(\n        _ session: GitURLSession = URLSession.shared,\n        owner: String,\n        repository: String,\n        state: GitHubOpenness = .open,\n        page: String = \"1\",\n        perPage: String = \"100\",\n        completion: @escaping (_ response: Result<[GitHubIssue], Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitHubIssueRouter.readIssues(configuration, owner, repository, page, perPage, state)\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: [GitHubIssue].self\n        ) { issues, error in\n\n            if let error {\n                completion(.failure(error))\n            } else {\n                if let issues {\n                    completion(.success(issues))\n                }\n            }\n        }\n    }\n\n    /**\n     Creates an issue in a repository.\n     - parameter session: GitURLSession, defaults to URLSession.sharedSession()\n     - parameter owner: The user or organization that owns the repository.\n     - parameter repository: The name of the repository.\n     - parameter title: The title of the issue.\n     - parameter body: The body text of the issue in GitHub-flavored Markdown format.\n     - parameter assignee: The name of the user to assign the issue to.\n                           This parameter is ignored if the user lacks push access to the repository.\n     - parameter labels: An array of label names to add to the issue. If the labels do not exist,\n                         GitHub will create them automatically.\n                         This parameter is ignored if the user lacks push access to the repository.\n     - parameter completion: Callback for the issue that is created.\n     */\n    @discardableResult\n    func postIssue(\n        _ session: GitURLSession = URLSession.shared,\n        owner: String,\n        repository: String,\n        title: String,\n        body: String? = nil,\n        assignee: String? = nil,\n        labels: [String] = [],\n        completion: @escaping (_ response: Result<GitHubIssue, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n\n        let router = GitHubIssueRouter.postIssue(configuration, owner, repository, title, body, assignee, labels)\n        let decoder = JSONDecoder()\n\n        decoder.dateDecodingStrategy = .formatted(GitTime.rfc3339DateFormatter)\n\n        return router.post(\n            session,\n            decoder: decoder,\n            expectedResultType: GitHubIssue.self\n        ) { issue, error in\n\n            if let error {\n                completion(.failure(error))\n            } else {\n                if let issue {\n                    completion(.success(issue))\n                }\n            }\n        }\n    }\n\n    /**\n     Edits an issue in a repository.\n     - parameter session: GitURLSession, defaults to URLSession.sharedSession()\n     - parameter owner: The user or organization that owns the repository.\n     - parameter repository: The name of the repository.\n     - parameter number: The number of the issue.\n     - parameter title: The title of the issue.\n     - parameter body: The body text of the issue in GitHub-flavored Markdown format.\n     - parameter assignee: The name of the user to assign the issue to.\n                           This parameter is ignored if the user lacks push access to the repository.\n     - parameter state: Whether the issue is open or closed.\n     - parameter completion: Callback for the issue that is created.\n     */\n    @discardableResult\n    func patchIssue(\n        _ session: GitURLSession = URLSession.shared,\n        owner: String,\n        repository: String,\n        number: Int,\n        title: String? = nil,\n        body: String? = nil,\n        assignee: String? = nil,\n        state: GitHubOpenness? = nil,\n        completion: @escaping (_ response: Result<GitHubIssue, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n\n        let router = GitHubIssueRouter.patchIssue(\n            configuration, owner, repository, number, title, body, assignee, state\n        )\n\n        return router.post(\n            session,\n            expectedResultType: GitHubIssue.self\n        ) { issue, error in\n\n            if let error {\n                completion(.failure(error))\n            } else {\n                if let issue {\n                    completion(.success(issue))\n                }\n            }\n        }\n    }\n\n    /// Posts a comment on an issue using the given body.\n    /// - Parameters:\n    ///   - session: GitURLSession, defaults to URLSession.sharedSession()\n    ///   - owner: The user or organization that owns the repository.\n    ///   - repository: The name of the repository.\n    ///   - number: The number of the issue.\n    ///   - body: The contents of the comment.\n    ///   - completion: Callback for the comment that is created.\n    @discardableResult\n    func commentIssue(\n        _ session: GitURLSession = URLSession.shared,\n        owner: String,\n        repository: String,\n        number: Int,\n        body: String,\n        completion: @escaping (_ response: Result<GitHubComment, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n\n        let router = GitHubIssueRouter.commentIssue(configuration, owner, repository, number, body)\n        let decoder = JSONDecoder()\n\n        decoder.dateDecodingStrategy = .formatted(GitTime.rfc3339DateFormatter)\n\n        return router.post(\n            session,\n            decoder: decoder,\n            expectedResultType: GitHubComment.self\n        ) { issue, error in\n\n            if let error {\n                completion(.failure(error))\n            } else {\n                if let issue {\n                    completion(.success(issue))\n                }\n            }\n        }\n    }\n\n    /// Fetches all comments for an issue\n    /// - Parameters:\n    /// - session: GitURLSession, defaults to URLSession.sharedSession()\n    /// - owner: The user or organization that owns the repository.\n    /// - repository: The name of the repository.\n    /// - number: The number of the issue.\n    /// - page: Current page for comments pagination. `1` by default.\n    /// - perPage: Number of comments per page. `100` by default.\n    /// - completion: Callback for the outcome of the fetch.\n    @discardableResult\n    func issueComments(\n        _ session: GitURLSession = URLSession.shared,\n        owner: String,\n        repository: String,\n        number: Int,\n        page: String = \"1\",\n        perPage: String = \"100\",\n        completion: @escaping (_ response: Result<[GitHubComment], Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n\n        let router = GitHubIssueRouter.readIssueComments(configuration, owner, repository, number, page, perPage)\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: [GitHubComment].self\n        ) { comments, error in\n\n            if let error {\n                completion(.failure(error))\n            } else {\n                if let comments {\n                    completion(.success(comments))\n                }\n            }\n        }\n    }\n\n    /// Edits a comment on an issue using the given body.\n    /// - Parameters:\n    ///   - session: GitURLSession, defaults to URLSession.sharedSession()\n    ///   - owner: The user or organization that owns the repository.\n    ///   - repository: The name of the repository.\n    ///   - number: The number of the comment.\n    ///   - body: The contents of the comment.\n    ///   - completion: Callback for the comment that is created.\n    @discardableResult\n    func patchIssueComment(\n        _ session: GitURLSession = URLSession.shared,\n        owner: String,\n        repository: String,\n        number: Int,\n        body: String,\n        completion: @escaping (_ response: Result<GitHubComment, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n\n        let router = GitHubIssueRouter.patchIssueComment(configuration, owner, repository, number, body)\n        let decoder = JSONDecoder()\n        decoder.dateDecodingStrategy = .formatted(GitTime.rfc3339DateFormatter)\n\n        return router.post(\n            session, decoder: decoder,\n            expectedResultType: GitHubComment.self\n        ) { issue, error in\n\n            if let error {\n                completion(.failure(error))\n            } else {\n                if let issue {\n                    completion(.success(issue))\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitHub/Model/GitHubPullRequest.swift",
    "content": "//\n//  GitHubPullRequest.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\nclass GitHubPullRequest: Codable {\n    private(set) var id: Int = -1\n    var url: URL?\n\n    var htmlURL: URL?\n    var diffURL: URL?\n    var patchURL: URL?\n    var issueURL: URL?\n    var commitsURL: URL?\n    var reviewCommentsURL: URL?\n    var reviewCommentURL: URL?\n    var commentsURL: URL?\n    var statusesURL: URL?\n\n    var title: String?\n    var body: String?\n\n    var assignee: GitHubUser?\n\n    var locked: Bool?\n    var createdAt: Date?\n    var updatedAt: Date?\n    var closedAt: Date?\n    var mergedAt: Date?\n\n    var user: GitHubUser?\n    var number: Int\n    var state: GitHubOpenness?\n\n    var head: GitHubPullRequest.Branch?\n    var base: GitHubPullRequest.Branch?\n\n    var requestedReviewers: [GitHubUser]?\n    var draft: Bool?\n\n    enum CodingKeys: String, CodingKey {\n        case id\n        case url\n        case diffURL = \"diff_url\"\n        case patchURL = \"patch_url\"\n        case issueURL = \"issue_url\"\n        case commitsURL = \"commits_url\"\n        case reviewCommentsURL = \"review_comments_url\"\n        case commentsURL = \"comments_url\"\n        case statusesURL = \"statuses_url\"\n        case htmlURL = \"html_url\"\n        case number\n        case state\n        case title\n        case body\n        case assignee\n        case locked\n        case user\n        case closedAt = \"closed_at\"\n        case createdAt = \"created_at\"\n        case updatedAt = \"updated_at\"\n        case mergedAt = \"merged_at\"\n        case head\n        case base\n        case requestedReviewers = \"requested_reviewers\"\n        case draft\n    }\n\n    class Branch: Codable {\n        var label: String?\n        var ref: String?\n        var sha: String?\n        var user: GitHubUser?\n        var repo: GitHubRepositories?\n    }\n}\n\nextension GitHubAccount {\n\n    /**\n     Get a single pull request\n     - parameter session: GitURLSession, defaults to URLSession.shared\n     - parameter owner: The user or organization that owns the repositories.\n     - parameter repository: The name of the repository.\n     - parameter number: The number of the PR to fetch.\n     - parameter completion: Callback for the outcome of the fetch.\n     */\n    @discardableResult\n    func pullRequest(\n        _ session: GitURLSession = URLSession.shared,\n        owner: String,\n        repository: String,\n        number: Int,\n        completion: @escaping (_ response: Result<GitHubPullRequest, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitHubPullRequestRouter.readPullRequest(configuration, owner, repository, \"\\(number)\")\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: GitHubPullRequest.self\n        ) { pullRequest, error in\n\n            if let error {\n                completion(.failure(error))\n            } else {\n                if let pullRequest {\n                    completion(.success(pullRequest))\n                }\n            }\n        }\n    }\n\n    /**\n     Get a list of pull requests\n     - parameter session: GitURLSession, defaults to URLSession.shared\n     - parameter owner: The user or organization that owns the repositories.\n     - parameter repository: The name of the repository.\n     - parameter base: Filter pulls by base branch name.\n     - parameter head: Filter pulls by user or organization and branch name.\n     - parameter state: Filter pulls by their state.\n     - parameter direction: The direction of the sort.\n     - parameter completion: Callback for the outcome of the fetch.\n     */\n    @discardableResult\n    func pullRequests(\n        _ session: GitURLSession = URLSession.shared,\n        owner: String,\n        repository: String,\n        base: String? = nil,\n        head: String? = nil,\n        state: GitHubOpenness = .open,\n        sort: GitSortType = .created,\n        direction: GitSortDirection = .desc,\n        completion: @escaping (_ response: Result<[GitHubPullRequest], Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitHubPullRequestRouter.readPullRequests(\n            configuration,\n            owner,\n            repository,\n            base,\n            head,\n            state,\n            sort,\n            direction\n        )\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: [GitHubPullRequest].self\n        ) { pullRequests, error in\n\n            if let error {\n                completion(.failure(error))\n            } else {\n                if let pullRequests {\n                    completion(.success(pullRequests))\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitHub/Model/GitHubRepositories.swift",
    "content": "//\n//  Repositories.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\nclass GitHubRepositories: Codable {\n    private(set) var id: Int = -1\n    private(set) var owner = GitHubUser()\n    var name: String?\n    var fullName: String?\n    private(set) var isPrivate: Bool = false\n    var repositoryDescription: String?\n    private(set) var isFork: Bool = false\n    var gitURL: String?\n    var sshURL: String?\n    var cloneURL: String?\n    var htmlURL: String?\n    private(set) var size: Int? = -1\n    var lastPush: Date?\n    var stargazersCount: Int?\n\n    enum CodingKeys: String, CodingKey {\n        case id\n        case owner\n        case name\n        case fullName = \"full_name\"\n        case isPrivate = \"private\"\n        case repositoryDescription = \"description\"\n        case isFork = \"fork\"\n        case gitURL = \"git_url\"\n        case sshURL = \"ssh_url\"\n        case cloneURL = \"clone_url\"\n        case htmlURL = \"html_url\"\n        case size\n        case lastPush = \"pushed_at\"\n        case stargazersCount = \"stargazers_count\"\n    }\n}\n\nextension GitHubAccount {\n\n    /**\n        Fetches the Repositories for a user or organization\n            - parameter session: GitURLSession, defaults to URLSession.shared\n            - parameter owner: The user or organization that owns the repositories. If `nil`,\n                               fetches repositories for the authenticated user.\n            - parameter page: Current page for repository pagination. `1` by default.\n            - parameter perPage: Number of repositories per page. `100` by default.\n            - parameter completion: Callback for the outcome of the fetch.\n     */\n    @discardableResult\n    func repositories(\n        _ session: GitURLSession = URLSession.shared,\n        owner: String? = nil,\n        page: String = \"1\",\n        perPage: String = \"100\",\n        completion: @escaping (_ response: Result<[GitHubRepositories], Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = (owner != nil)\n            ? GitHubRepositoryRouter.readRepositories(configuration, owner!, page, perPage)\n            : GitHubRepositoryRouter.readAuthenticatedRepositories(configuration, page, perPage)\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: [GitHubRepositories].self\n        ) { repos, error in\n            if let error {\n                completion(.failure(error))\n            }\n\n            if let repos {\n                completion(.success(repos))\n            }\n        }\n    }\n\n    /**\n         Fetches a repository for a user or organization\n         - parameter session: GitURLSession, defaults to URLSession.shared\n         - parameter owner: The user or organization that owns the repositories.\n         - parameter name: The name of the repository to fetch.\n         - parameter completion: Callback for the outcome of the fetch.\n     */\n    @discardableResult\n    func repository(\n        _ session: GitURLSession = URLSession.shared,\n        owner: String,\n        name: String,\n        completion: @escaping (_ response: Result<GitHubRepositories, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitHubRepositoryRouter.readRepository(configuration, owner, name)\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: GitHubRepositories.self\n        ) { repo, error in\n            if let error {\n                completion(.failure(error))\n            } else {\n                if let repo {\n                    completion(.success(repo))\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitHub/Model/GitHubReview.swift",
    "content": "//\n//  GitHubReview.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\n// TODO: DOCS (Nanashi Li)\nstruct GitHubReview {\n    let body: String\n    let commitID: String\n    let id: Int\n    let state: State\n    let submittedAt: Date\n    let user: GitHubUser\n}\n\nextension GitHubReview: Codable {\n    enum CodingKeys: String, CodingKey {\n        case body\n        case commitID = \"commit_id\"\n        case id\n        case state\n        case submittedAt = \"submitted_at\"\n        case user\n    }\n}\n\nextension GitHubReview {\n    enum State: String, Codable, Equatable {\n        case approved = \"APPROVED\"\n        case changesRequested = \"CHANGES_REQUESTED\"\n        case comment = \"COMMENTED\"\n        case dismissed = \"DISMISSED\"\n        case pending = \"PENDING\"\n    }\n}\n\nextension GitHubAccount {\n\n    @discardableResult\n    func listReviews(\n        _ session: GitURLSession = URLSession.shared,\n        owner: String,\n        repository: String,\n        pullRequestNumber: Int,\n        completion: @escaping (_ response: Result<[GitHubReview], Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitHubReviewsRouter.listReviews(configuration, owner, repository, pullRequestNumber)\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: [GitHubReview].self\n        ) { pullRequests, error in\n\n            if let error {\n                completion(.failure(error))\n            } else {\n                if let pullRequests {\n                    completion(.success(pullRequests))\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitHub/Model/GitHubUser.swift",
    "content": "//\n//  GitHubUser.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\nclass GitHubUser: Codable {\n    private(set) var id: Int = -1\n    var login: String?\n    var avatarURL: String?\n    var gravatarID: String?\n    var type: String?\n    var name: String?\n    var company: String?\n    var email: String?\n    var numberOfPublicRepos: Int?\n    var numberOfPublicGists: Int?\n    var numberOfPrivateRepos: Int?\n    var nodeID: String?\n    var url: String?\n    var htmlURL: String?\n    var gistsURL: String?\n    var starredURL: String?\n    var subscriptionsURL: String?\n    var reposURL: String?\n    var eventsURL: String?\n    var receivedEventsURL: String?\n    var createdAt: Date?\n    var updatedAt: Date?\n    var numberOfPrivateGists: Int?\n    var numberOfOwnPrivateRepos: Int?\n    var twoFactorAuthenticationEnabled: Bool?\n\n    enum CodingKeys: String, CodingKey {\n        case id\n        case login\n        case avatarURL = \"avatar_url\"\n        case gravatarID = \"gravatar_id\"\n        case type\n        case name\n        case company\n        case email\n        case numberOfPublicRepos = \"public_repos\"\n        case numberOfPublicGists = \"public_gists\"\n        case numberOfPrivateRepos = \"total_private_repos\"\n        case nodeID = \"node_id\"\n        case url\n        case htmlURL = \"html_url\"\n        case gistsURL = \"gists_url\"\n        case starredURL = \"starred_url\"\n        case subscriptionsURL = \"subscriptions_url\"\n        case reposURL = \"repos_url\"\n        case eventsURL = \"events_url\"\n        case receivedEventsURL = \"received_events_url\"\n        case createdAt = \"created_at\"\n        case updatedAt = \"updated_at\"\n        case numberOfPrivateGists = \"private_gists\"\n        case numberOfOwnPrivateRepos = \"owned_private_repos\"\n        case twoFactorAuthenticationEnabled = \"two_factor_authentication\"\n    }\n}\n\nextension GitHubAccount {\n    /**\n         Fetches a user or organization\n         - parameter session: GitURLSession, defaults to URLSession.shared\n         - parameter name: The name of the user or organization.\n         - parameter completion: Callback for the outcome of the fetch.\n     */\n    @discardableResult\n    func user(\n        _ session: GitURLSession = URLSession.shared,\n        name: String,\n        completion: @escaping (_ response: Result<GitHubUser, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitHubUserRouter.readUser(name, configuration)\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: GitHubUser.self\n        ) { user, error in\n            if let error {\n                completion(.failure(error))\n            } else {\n                if let user {\n                    completion(.success(user))\n                }\n            }\n        }\n    }\n\n    /**\n         Fetches the authenticated user\n         - parameter session: GitURLSession, defaults to URLSession.shared\n         - parameter completion: Callback for the outcome of the fetch.\n     */\n    @discardableResult\n    func me(\n        _ session: GitURLSession = URLSession.shared,\n        completion: @escaping (_ response: Result<GitHubUser, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitHubUserRouter.readAuthenticatedUser(configuration)\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: GitHubUser.self\n        ) { user, error in\n            if let error {\n                completion(.failure(error))\n            } else {\n                if let user {\n                    completion(.success(user))\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitHub/PublicKey.swift",
    "content": "//\n//  PublicKey.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\n// TODO: DOCS (Nanashi Li)\nextension GitHubAccount {\n    func postPublicKey(\n        _ session: GitURLSession = URLSession.shared,\n        publicKey: String,\n        title: String,\n        completion: @escaping (_ response: Result<String, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitHubPublicKeyRouter.postPublicKey(publicKey, title, configuration)\n\n        return router.postJSON(\n            session,\n            expectedResultType: [String: AnyObject].self\n        ) { json, error in\n\n            if let error {\n                completion(.failure(error))\n            } else {\n                if json != nil {\n                    completion(.success(publicKey))\n                }\n            }\n        }\n    }\n}\n\nenum GitHubPublicKeyRouter: GitJSONPostRouter {\n    case postPublicKey(String, String, GitRouterConfiguration)\n\n    var configuration: GitRouterConfiguration? {\n        switch self {\n        case let .postPublicKey(_, _, config): return config\n        }\n    }\n\n    var method: GitHTTPMethod {\n        switch self {\n        case .postPublicKey:\n            return .POST\n        }\n    }\n\n    var encoding: GitHTTPEncoding {\n        switch self {\n        case .postPublicKey:\n            return .json\n        }\n    }\n\n    var path: String {\n        switch self {\n        case .postPublicKey:\n            return \"user/keys\"\n        }\n    }\n\n    var params: [String: Any] {\n        switch self {\n        case let .postPublicKey(publicKey, title, _):\n            return [\"title\": title, \"key\": publicKey]\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitHub/Routers/GitHubGistRouter.swift",
    "content": "//\n//  GitHubGistRouter.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanshi Li on 2022/03/31.\n//\n\nimport Foundation\n\nenum GitHubGistRouter: GitJSONPostRouter {\n    case readAuthenticatedGists(GitRouterConfiguration, String, String)\n    case readGists(GitRouterConfiguration, String, String, String)\n    case readGist(GitRouterConfiguration, String)\n    case postGistFile(GitRouterConfiguration, String, String, String, Bool)\n    case patchGistFile(GitRouterConfiguration, String, String, String, String)\n\n    var method: GitHTTPMethod {\n        switch self {\n        case .postGistFile, .patchGistFile:\n            return .POST\n        default:\n            return .GET\n        }\n    }\n\n    var encoding: GitHTTPEncoding {\n        switch self {\n        case .postGistFile, .patchGistFile:\n            return .json\n        default:\n            return .url\n        }\n    }\n\n    var configuration: GitRouterConfiguration? {\n        switch self {\n        case let .readAuthenticatedGists(config, _, _): return config\n        case let .readGists(config, _, _, _): return config\n        case let .readGist(config, _): return config\n        case let .postGistFile(config, _, _, _, _): return config\n        case let .patchGistFile(config, _, _, _, _): return config\n        }\n    }\n\n    var params: [String: Any] {\n        switch self {\n        case let .readAuthenticatedGists(_, page, perPage):\n            return [\"per_page\": perPage, \"page\": page]\n        case let .readGists(_, _, page, perPage):\n            return [\"per_page\": perPage, \"page\": page]\n        case .readGist:\n            return [:]\n        case let .postGistFile(_, description, filename, fileContent, publicAccess):\n            var params = [String: Any]()\n            params[\"public\"] = publicAccess\n            params[\"description\"] = description\n            var file = [String: Any]()\n            file[\"content\"] = fileContent\n            var files = [String: Any]()\n            files[filename] = file\n            params[\"files\"] = files\n            return params\n        case let .patchGistFile(_, _, description, filename, fileContent):\n            var params = [String: Any]()\n            params[\"description\"] = description\n            var file = [String: Any]()\n            file[\"content\"] = fileContent\n            var files = [String: Any]()\n            files[filename] = file\n            params[\"files\"] = files\n            return params\n        }\n    }\n\n    var path: String {\n        switch self {\n        case .readAuthenticatedGists:\n            return \"gists\"\n        case let .readGists(_, owner, _, _):\n            return \"users/\\(owner)/gists\"\n        case let .readGist(_, id):\n            return \"gists/\\(id)\"\n        case .postGistFile:\n            return \"gists\"\n        case let .patchGistFile(_, id, _, _, _):\n            return \"gists/\\(id)\"\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitHub/Routers/GitHubIssueRouter.swift",
    "content": "//\n//  GitHubIssueRouter.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nenum GitHubIssueRouter: GitJSONPostRouter {\n    case readAuthenticatedIssues(GitRouterConfiguration, String, String, GitHubOpenness)\n    case readIssue(GitRouterConfiguration, String, String, Int)\n    case readIssues(GitRouterConfiguration, String, String, String, String, GitHubOpenness)\n    case postIssue(GitRouterConfiguration, String, String, String, String?, String?, [String])\n    case patchIssue(GitRouterConfiguration, String, String, Int, String?, String?, String?, GitHubOpenness?)\n    case commentIssue(GitRouterConfiguration, String, String, Int, String)\n    case readIssueComments(GitRouterConfiguration, String, String, Int, String, String)\n    case patchIssueComment(GitRouterConfiguration, String, String, Int, String)\n\n    var method: GitHTTPMethod {\n        switch self {\n        case .postIssue, .patchIssue, .commentIssue, .patchIssueComment:\n            return .POST\n        default:\n            return .GET\n        }\n    }\n\n    var encoding: GitHTTPEncoding {\n        switch self {\n        case .postIssue, .patchIssue, .commentIssue, .patchIssueComment:\n            return .json\n        default:\n            return .url\n        }\n    }\n\n    var configuration: GitRouterConfiguration? {\n        switch self {\n        case let .readAuthenticatedIssues(config, _, _, _): return config\n        case let .readIssue(config, _, _, _): return config\n        case let .readIssues(config, _, _, _, _, _): return config\n        case let .postIssue(config, _, _, _, _, _, _): return config\n        case let .patchIssue(config, _, _, _, _, _, _, _): return config\n        case let .commentIssue(config, _, _, _, _): return config\n        case let .readIssueComments(config, _, _, _, _, _): return config\n        case let .patchIssueComment(config, _, _, _, _): return config\n        }\n    }\n\n    var params: [String: Any] {\n        switch self {\n        case let .readAuthenticatedIssues(_, page, perPage, state):\n            return [\"per_page\": perPage, \"page\": page, \"state\": state.rawValue]\n        case .readIssue:\n            return [:]\n        case let .readIssues(_, _, _, page, perPage, state):\n            return [\"per_page\": perPage, \"page\": page, \"state\": state.rawValue]\n        case let .postIssue(_, _, _, title, body, assignee, labels):\n            var params: [String: Any] = [\"title\": title]\n            if let body {\n                params[\"body\"] = body\n            }\n            if let assignee {\n                params[\"assignee\"] = assignee\n            }\n            if !labels.isEmpty {\n                params[\"labels\"] = labels\n            }\n            return params\n        case let .patchIssue(_, _, _, _, title, body, assignee, state):\n            var params: [String: String] = [:]\n            if let title {\n                params[\"title\"] = title\n            }\n            if let body {\n                params[\"body\"] = body\n            }\n            if let assignee {\n                params[\"assignee\"] = assignee\n            }\n            if let state {\n                params[\"state\"] = state.rawValue\n            }\n            return params\n        case let .commentIssue(_, _, _, _, body):\n            return [\"body\": body]\n        case let .readIssueComments(_, _, _, _, page, perPage):\n            return [\"per_page\": perPage, \"page\": page]\n        case let .patchIssueComment(_, _, _, _, body):\n            return [\"body\": body]\n        }\n    }\n\n    var path: String {\n        switch self {\n        case .readAuthenticatedIssues:\n            return \"issues\"\n        case let .readIssue(_, owner, repository, number):\n            return \"repos/\\(owner)/\\(repository)/issues/\\(number)\"\n        case let .readIssues(_, owner, repository, _, _, _):\n            return \"repos/\\(owner)/\\(repository)/issues\"\n        case let .postIssue(_, owner, repository, _, _, _, _):\n            return \"repos/\\(owner)/\\(repository)/issues\"\n        case let .patchIssue(_, owner, repository, number, _, _, _, _):\n            return \"repos/\\(owner)/\\(repository)/issues/\\(number)\"\n        case let .commentIssue(_, owner, repository, number, _):\n            return \"repos/\\(owner)/\\(repository)/issues/\\(number)/comments\"\n        case let .readIssueComments(_, owner, repository, number, _, _):\n            return \"repos/\\(owner)/\\(repository)/issues/\\(number)/comments\"\n        case let .patchIssueComment(_, owner, repository, number, _):\n            return \"repos/\\(owner)/\\(repository)/issues/comments/\\(number)\"\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitHub/Routers/GitHubPullRequestRouter.swift",
    "content": "//\n//  GitHubPullRequestRouter.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanshi Li on 2022/03/31.\n//\n\nimport Foundation\n\nenum GitHubPullRequestRouter: GitJSONPostRouter {\n    case readPullRequest(GitRouterConfiguration, String, String, String)\n    case readPullRequests(\n        GitRouterConfiguration, String, String, String?, String?, GitHubOpenness, GitSortType, GitSortDirection\n    )\n\n    var method: GitHTTPMethod {\n        switch self {\n        case .readPullRequest,\n             .readPullRequests:\n            return .GET\n        }\n    }\n\n    var encoding: GitHTTPEncoding {\n        switch self {\n        default:\n            return .url\n        }\n    }\n\n    var configuration: GitRouterConfiguration? {\n        switch self {\n        case let .readPullRequest(config, _, _, _): return config\n        case let .readPullRequests(config, _, _, _, _, _, _, _): return config\n        }\n    }\n\n    var params: [String: Any] {\n        switch self {\n        case .readPullRequest:\n            return [:]\n        case let .readPullRequests(_, _, _, base, head, state, sort, direction):\n            var parameters = [\n                \"state\": state.rawValue,\n                \"sort\": sort.rawValue,\n                \"direction\": direction.rawValue\n            ]\n\n            if let base {\n                parameters[\"base\"] = base\n            }\n\n            if let head {\n                parameters[\"head\"] = head\n            }\n\n            return parameters\n        }\n    }\n\n    var path: String {\n        switch self {\n        case let .readPullRequest(_, owner, repository, number):\n            return \"repos/\\(owner)/\\(repository)/pulls/\\(number)\"\n        case let .readPullRequests(_, owner, repository, _, _, _, _, _):\n            return \"repos/\\(owner)/\\(repository)/pulls\"\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitHub/Routers/GitHubRepositoryRouter.swift",
    "content": "//\n//  GitHubRepositoryRouter.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanshi Li on 2022/03/31.\n//\n\nimport Foundation\n\nenum GitHubRepositoryRouter: GitRouter {\n    case readRepositories(GitRouterConfiguration, String, String, String)\n    case readAuthenticatedRepositories(GitRouterConfiguration, String, String)\n    case readRepository(GitRouterConfiguration, String, String)\n\n    var configuration: GitRouterConfiguration? {\n        switch self {\n        case let .readRepositories(config, _, _, _): return config\n        case let .readAuthenticatedRepositories(config, _, _): return config\n        case let .readRepository(config, _, _): return config\n        }\n    }\n\n    var method: GitHTTPMethod {\n        .GET\n    }\n\n    var encoding: GitHTTPEncoding {\n        .url\n    }\n\n    var params: [String: Any] {\n        switch self {\n        case let .readRepositories(_, _, page, perPage):\n            return [\"per_page\": perPage, \"page\": page]\n        case let .readAuthenticatedRepositories(_, page, perPage):\n            return [\"per_page\": perPage, \"page\": page]\n        case .readRepository:\n            return [:]\n        }\n    }\n\n    var path: String {\n        switch self {\n        case let .readRepositories(_, owner, _, _):\n            return \"users/\\(owner)/repos\"\n        case .readAuthenticatedRepositories:\n            return \"user/repos\"\n        case let .readRepository(_, owner, name):\n            return \"repos/\\(owner)/\\(name)\"\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitHub/Routers/GitHubReviewsRouter.swift",
    "content": "//\n//  GitHubReviewsRouter.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanshi Li on 2022/03/31.\n//\n\nimport Foundation\n\nenum GitHubReviewsRouter: GitJSONPostRouter {\n    case listReviews(GitRouterConfiguration, String, String, Int)\n\n    var method: GitHTTPMethod {\n        switch self {\n        case .listReviews:\n            return .GET\n        }\n    }\n\n    var encoding: GitHTTPEncoding {\n        switch self {\n        default:\n            return .url\n        }\n    }\n\n    var configuration: GitRouterConfiguration? {\n        switch self {\n        case let .listReviews(config, _, _, _):\n            return config\n        }\n    }\n\n    var params: [String: Any] {\n        switch self {\n        case .listReviews:\n            return [:]\n        }\n    }\n\n    var path: String {\n        switch self {\n        case let .listReviews(_, owner, repository, pullRequestNumber):\n            return \"repos/\\(owner)/\\(repository)/pulls/\\(pullRequestNumber)/reviews\"\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitHub/Routers/GitHubRouter.swift",
    "content": "//\n//  GitHubRouter.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanshi Li on 2022/03/31.\n//\n\nimport Foundation\n\nenum GitHubRouter: GitJSONPostRouter {\n    case deleteReference(GitRouterConfiguration, String, String, String)\n\n    var configuration: GitRouterConfiguration? {\n        switch self {\n        case let .deleteReference(config, _, _, _): return config\n        }\n    }\n\n    var method: GitHTTPMethod {\n        switch self {\n        case .deleteReference:\n            return .DELETE\n        }\n    }\n\n    var encoding: GitHTTPEncoding {\n        switch self {\n        case .deleteReference:\n            return .url\n        }\n    }\n\n    var params: [String: Any] {\n        switch self {\n        case .deleteReference:\n            return [:]\n        }\n    }\n\n    var path: String {\n        switch self {\n        case let .deleteReference(_, owner, repo, reference):\n            return \"repos/\\(owner)/\\(repo)/git/refs/\\(reference)\"\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitHub/Routers/GitHubUserRouter.swift",
    "content": "//\n//  GitHubUserRouter.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanshi Li on 2022/03/31.\n//\n\nimport Foundation\n\nenum GitHubUserRouter: GitRouter {\n    case readAuthenticatedUser(GitRouterConfiguration)\n    case readUser(String, GitRouterConfiguration)\n\n    var configuration: GitRouterConfiguration? {\n        switch self {\n        case let .readAuthenticatedUser(config): return config\n        case let .readUser(_, config): return config\n        }\n    }\n\n    var method: GitHTTPMethod {\n        .GET\n    }\n\n    var encoding: GitHTTPEncoding {\n        .url\n    }\n\n    var path: String {\n        switch self {\n        case .readAuthenticatedUser:\n            return \"user\"\n        case let .readUser(username, _):\n            return \"users/\\(username)\"\n        }\n    }\n\n    var params: [String: Any] {\n        [:]\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitLab/GitLabAccount.swift",
    "content": "//\n//  GitLabAccount.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\n// TODO: DOCS (Nanashi Li)\n\nstruct GitLabAccount {\n    let configuration: GitRouterConfiguration\n\n    init(_ config: GitRouterConfiguration = GitLabTokenConfiguration()) {\n        configuration = config\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitLab/GitLabConfiguration.swift",
    "content": "//\n//  GitLabConfiguration.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nstruct GitLabTokenConfiguration: GitRouterConfiguration {\n    let provider = SourceControlAccount.Provider.gitlab\n    var apiEndpoint: String?\n    var accessToken: String?\n    let errorDomain: String? = \"com.codeedit.models.accounts.gitlab\"\n\n    init(_ token: String? = nil, url: String? = nil) {\n        apiEndpoint = url ?? provider.apiURL?.absoluteString\n        accessToken = token\n    }\n}\n\nstruct GitLabPrivateTokenConfiguration: GitRouterConfiguration {\n    let provider = SourceControlAccount.Provider.gitlab\n    var apiEndpoint: String?\n    var accessToken: String?\n    let errorDomain: String? = \"com.codeedit.models.accounts.gitlab\"\n\n    init(_ token: String? = nil, url: String? = nil) {\n        apiEndpoint = url ?? provider.apiURL?.absoluteString\n        accessToken = token\n    }\n\n    var accessTokenFieldName: String {\n        \"private_token\"\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitLab/GitLabOAuthConfiguration.swift",
    "content": "//\n//  GitLabOAuthConfiguration.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nstruct GitLabOAuthConfiguration: GitRouterConfiguration {\n    let provider = SourceControlAccount.Provider.gitlab\n    var apiEndpoint: String?\n    var accessToken: String?\n    let token: String\n    let secret: String\n    let redirectURI: String\n    let webEndpoint: String?\n    let errorDomain = \"com.codeedit.models.accounts.gitlab\"\n\n    init(\n        _ url: String? = nil,\n        webURL: String? = nil,\n        token: String,\n        secret: String,\n        redirectURI: String\n    ) {\n        apiEndpoint = url ?? provider.apiURL?.absoluteString\n        webEndpoint = webURL ?? provider.baseURL?.absoluteString\n        self.token = token\n        self.secret = secret\n        self.redirectURI = redirectURI\n    }\n\n    func authenticate() -> URL? {\n        GitLabOAuthRouter.authorize(self, redirectURI).URLRequest?.url\n    }\n\n    func authorize(\n        _ session: GitURLSession = URLSession.shared,\n        code: String,\n        completion: @escaping (_ config: GitLabTokenConfiguration) -> Void\n    ) {\n        let request = GitLabOAuthRouter.accessToken(self, code, redirectURI).URLRequest\n        if let request {\n            let task = session.dataTask(with: request) { data, response, _ in\n                if let response = response as? HTTPURLResponse {\n                    if response.statusCode != 200 {\n                        return\n                    } else {\n                        guard let data else {\n                            return\n                        }\n                        do {\n                            let json = try JSONSerialization.jsonObject(\n                                with: data,\n                                options: .allowFragments\n                            ) as? [String: Any]\n                            if let json, let accessToken = json[\"access_token\"] as? String {\n                                let config = GitLabTokenConfiguration(accessToken, url: self.apiEndpoint ?? \"\")\n                                completion(config)\n                            }\n                        } catch {\n                            return\n                        }\n                    }\n                }\n            }\n            task.resume()\n        }\n    }\n\n    func handleOpenURL(\n        _ session: GitURLSession = URLSession.shared,\n        url: URL,\n        completion: @escaping (_ config: GitLabTokenConfiguration) -> Void\n    ) {\n        if let code = url.absoluteString.components(separatedBy: \"=\").last {\n            authorize(session, code: code) { (config) in\n                completion(config)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitLab/Model/GitLabAccountModel.swift",
    "content": "//\n//  GitLabAccount.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Wesley de Groot on 02/04/2022.\n//\n\nimport Foundation\n\nextension GitLabAccount {\n    /**\n     Fetches the Projects for which the authenticated user is a member.\n     - parameter page: Current page for project pagination. `1` by default.\n     - parameter perPage: Number of projects per page. `100` by default.\n     - parameter archived: Limit by archived status. Default is false, set to `true` to only show archived projects.\n     - parameter visibility: Limit by visibility `public`, `internal` or `private`. Default is `\"\"`\n     - parameter orderBy: Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`,\n     or `last_activity_at` fields. Default is `created_at`.\n     - parameter sort: Return projects sorted in asc or desc order. Default is `desc`.\n     - parameter search: Return list of authorized projects matching the search criteria. Default is `\"\"`\n     - parameter simple: Return only the ID, URL, name, and path of each project. Default is false,\n     set to `true` to only show simple info.\n     - parameter completion: Callback for the outcome of the fetch.\n     */\n    func projects(\n        _ session: GitURLSession = URLSession.shared,\n        page: String = \"1\",\n        perPage: String = \"20\",\n        archived: Bool = false,\n        visibility: GitLabVisibility = GitLabVisibility.all,\n        orderBy: GitLabOrderBy = GitLabOrderBy.creationDate,\n        sort: GitLabSort = GitLabSort.descending,\n        search: String = \"\",\n        simple: Bool = false,\n        completion: @escaping (_ response: Result<[GitLabProject], Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitLabProjectRouter.readAuthenticatedProjects(\n            configuration: configuration,\n            page: page,\n            perPage: perPage,\n            archived: archived,\n            visibility: visibility,\n            orderBy: orderBy,\n            sort: sort,\n            search: search,\n            simple: simple\n        )\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: GitLabProject.self\n        ) { json, error in\n\n            if let error {\n                completion(Result.failure(error))\n            }\n\n            if let json {\n                completion(Result.success([json]))\n            }\n        }\n    }\n\n    /**\n     Fetches project for a specified ID.\n     - parameter id: The ID or namespace/project-name of the project.\n     Make sure that the namespace/project-name is URL-encoded, eg. \"%2F\" for \"/\".\n     - parameter completion: Callback for the outcome of the fetch.\n     */\n    func project(\n        _ session: GitURLSession = URLSession.shared,\n        id: String,\n        completion: @escaping (_ response: Result<GitLabProject, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitLabProjectRouter.readSingleProject(configuration: configuration, id: id)\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: GitLabProject.self\n        ) { json, error in\n\n            if let error {\n                completion(Result.failure(error))\n            }\n\n            if let json {\n                completion(Result.success(json))\n            }\n        }\n    }\n\n    /**\n     Fetches the Projects which the authenticated user can see.\n     - parameter page: Current page for project pagination. `1` by default.\n     - parameter perPage: Number of projects per page. `100` by default.\n     - parameter archived: Limit by archived status. Default is false, set to `true` to only show archived projects.\n     - parameter visibility: Limit by visibility `public`, `internal` or `private`. Default is `\"\"`\n     - parameter orderBy: Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`,\n     or `last_activity_at` fields. Default is `created_at`.\n     - parameter sort: Return projects sorted in asc or desc order. Default is `desc`.\n     - parameter search: Return list of authorized projects matching the search criteria. Default is `\"\"`\n     - parameter simple: Return only the ID, URL, name, and path of each project. Default is false,\n     set to `true` to only show simple info.\n     - parameter completion: Callback for the outcome of the fetch.\n     */\n    func visibleProjects(\n        _ session: GitURLSession = URLSession.shared,\n        page: String = \"1\",\n        perPage: String = \"20\",\n        archived: Bool = false,\n        visibility: GitLabVisibility = GitLabVisibility.all,\n        orderBy: GitLabOrderBy = GitLabOrderBy.creationDate,\n        sort: GitLabSort = GitLabSort.descending,\n        search: String = \"\",\n        simple: Bool = false,\n        completion: @escaping (_ response: Result<GitLabProject, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitLabProjectRouter.readVisibleProjects(\n            configuration: configuration,\n            page: page,\n            perPage: perPage,\n            archived: archived,\n            visibility: visibility,\n            orderBy: orderBy,\n            sort: sort,\n            search: search,\n            simple: simple\n        )\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: GitLabProject.self\n        ) { json, error in\n\n            if let error {\n                completion(Result.failure(error))\n            }\n\n            if let json {\n                completion(Result.success(json))\n            }\n        }\n    }\n\n    /**\n     Fetches the Projects which are owned by the authenticated user.\n     - parameter page: Current page for project pagination. `1` by default.\n     - parameter perPage: Number of projects per page. `100` by default.\n     - parameter archived: Limit by archived status. Default is false, set to `true` to only show archived projects.\n     - parameter visibility: Limit by visibility `public`, `internal` or `private`. Default is `\"\"`\n     - parameter orderBy: Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`,\n     or `last_activity_at` fields. Default is `created_at`.\n     - parameter sort: Return projects sorted in asc or desc order. Default is `desc`.\n     - parameter search: Return list of authorized projects matching the search criteria. Default is `\"\"`\n     - parameter simple: Return only the ID, URL, name, and path of each project. Default is false,\n     set to `true` to only show simple info.\n     - parameter completion: Callback for the outcome of the fetch.\n     */\n    func ownedProjects(\n        _ session: GitURLSession = URLSession.shared,\n        page: String = \"1\",\n        perPage: String = \"20\",\n        archived: Bool = false,\n        visibility: GitLabVisibility = GitLabVisibility.all,\n        orderBy: GitLabOrderBy = GitLabOrderBy.creationDate,\n        sort: GitLabSort = GitLabSort.descending,\n        search: String = \"\",\n        simple: Bool = false,\n        completion: @escaping (_ response: Result<GitLabProject, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitLabProjectRouter.readOwnedProjects(\n            configuration: configuration,\n            page: page,\n            perPage: perPage,\n            archived: archived,\n            visibility: visibility,\n            orderBy: orderBy,\n            sort: sort,\n            search: search,\n            simple: simple\n        )\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: GitLabProject.self\n        ) { json, error in\n\n            if let error {\n                completion(Result.failure(error))\n            }\n\n            if let json {\n                completion(Result.success(json))\n            }\n        }\n    }\n\n    /**\n     Fetches the Projects which are starred by the authenticated user.\n     - parameter page: Current page for project pagination. `1` by default.\n     - parameter perPage: Number of projects per page. `100` by default.\n     - parameter archived: Limit by archived status. Default is false, set to `true` to only show archived projects.\n     - parameter visibility: Limit by visibility `public`, `internal` or `private`. Default is `\"\"`\n     - parameter orderBy: Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`,\n     or `last_activity_at` fields. Default is `created_at`.\n     - parameter sort: Return projects sorted in asc or desc order. Default is `desc`.\n     - parameter search: Return list of authorized projects matching the search criteria. Default is `\"\"`\n     - parameter simple: Return only the ID, URL, name, and path of each project.\n     Default is false, set to `true` to only show simple info.\n     - parameter completion: Callback for the outcome of the fetch.\n     */\n    func starredProjects(\n        _ session: GitURLSession = URLSession.shared,\n        page: String = \"1\",\n        perPage: String = \"20\",\n        archived: Bool = false,\n        visibility: GitLabVisibility = GitLabVisibility.all,\n        orderBy: GitLabOrderBy = GitLabOrderBy.creationDate,\n        sort: GitLabSort = GitLabSort.descending,\n        search: String = \"\",\n        simple: Bool = false,\n        completion: @escaping (_ response: Result<GitLabProject, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitLabProjectRouter.readStarredProjects(\n            configuration: configuration,\n            page: page,\n            perPage: perPage,\n            archived: archived,\n            visibility: visibility,\n            orderBy: orderBy,\n            sort: sort,\n            search: search,\n            simple: simple\n        )\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: GitLabProject.self\n        ) { json, error in\n\n            if let error {\n                completion(Result.failure(error))\n            }\n\n            if let json {\n                completion(Result.success(json))\n            }\n        }\n    }\n\n    /**\n     Fetches all GitLab projects in the server **(admin only)**.\n     - parameter page: Current page for project pagination. `1` by default.\n     - parameter perPage: Number of projects per page. `100` by default.\n     - parameter archived: Limit by archived status. Default is false, set to `true` to only show archived projects.\n     - parameter visibility: Limit by visibility `public`, `internal` or `private`. Default is `\"\"`\n     - parameter orderBy: Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`,\n     or `last_activity_at` fields. Default is `created_at`.\n     - parameter sort: Return projects sorted in asc or desc order. Default is `desc`.\n     - parameter search: Return list of authorized projects matching the search criteria. Default is `\"\"`\n     - parameter simple: Return only the ID, URL, name, and path of each project.\n     Default is false, set to `true` to only show simple info.\n     - parameter completion: Callback for the outcome of the fetch.\n     */\n    func allProjects(\n        _ session: GitURLSession = URLSession.shared,\n        page: String = \"1\",\n        perPage: String = \"20\",\n        archived: Bool = false,\n        visibility: GitLabVisibility = GitLabVisibility.all,\n        orderBy: GitLabOrderBy = GitLabOrderBy.creationDate,\n        sort: GitLabSort = GitLabSort.descending,\n        search: String = \"\",\n        simple: Bool = false,\n        completion: @escaping (_ response: Result<GitLabProject, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitLabProjectRouter.readAllProjects(\n            configuration: configuration,\n            page: page,\n            perPage: perPage,\n            archived: archived,\n            visibility: visibility,\n            orderBy: orderBy,\n            sort: sort,\n            search: search,\n            simple: simple\n        )\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: GitLabProject.self\n        ) { json, error in\n\n            if let error {\n                completion(Result.failure(error))\n            }\n\n            if let json {\n                completion(Result.success(json))\n            }\n        }\n    }\n\n    /**\n     Fetches the events for the specified project. Sorted from newest to oldest.\n     - parameter page: Current page for project pagination. `1` by default.\n     - parameter perPage: Number of projects per page. `100` by default.\n     - parameter id: The ID or NAMESPACE/PROJECT_NAME of the project.\n     - parameter completion: Callback for the outcome of the fetch.\n     */\n    func projectEvents(\n        _ session: GitURLSession = URLSession.shared,\n        id: String,\n        page: String = \"1\",\n        perPage: String = \"20\",\n        completion: @escaping (_ response: Result<GitLabEvent, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitLabProjectRouter.readProjectEvents(\n            configuration: configuration,\n            id: id,\n            page: page,\n            perPage: perPage\n        )\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: GitLabEvent.self\n        ) { json, error in\n\n            if let error {\n                completion(Result.failure(error))\n            }\n\n            if let json {\n                completion(Result.success(json))\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitLab/Model/GitLabAvatarURL.swift",
    "content": "//\n//  GitLabAvatarURL.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nclass GitLabAvatarURL: Codable {\n    var url: URL?\n\n    init(_ json: [String: AnyObject]) {\n        if let urlString = json[\"url\"] as? String, let urlFromString = URL(string: urlString) {\n            url = urlFromString\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitLab/Model/GitLabCommit.swift",
    "content": "//\n//  GitLabCommit.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nclass GitLabCommit: Codable {\n    var id: String\n    var shortID: String?\n    var title: String?\n    var authorName: String?\n    var authorEmail: String?\n    var committerName: String?\n    var committerEmail: String?\n    var createdAt: Date?\n    var message: String?\n    var committedDate: Date?\n    var authoredDate: Date?\n    var parentIDs: [String]?\n    var stats: GitLabCommitStats?\n    var status: String?\n\n    enum CodingKeys: String, CodingKey {\n        case id\n        case shortID = \"short_id\"\n        case title\n        case authorName = \"author_name\"\n        case authorEmail = \"author_email\"\n        case committerName = \"committer_name\"\n        case committerEmail = \"committer_email\"\n        case createdAt = \"created_at\"\n        case message\n        case committedDate = \"committed_date\"\n        case authoredDate = \"authored_date\"\n        case parentIDs = \"parent_ids\"\n        case stats\n        case status\n    }\n}\n\nclass GitLabCommitStats: Codable {\n    var additions: Int?\n    var deletions: Int?\n    var total: Int?\n\n    enum CodingKeys: String, CodingKey {\n        case additions\n        case deletions\n        case total\n    }\n}\n\nclass GitLabCommitDiff: Codable {\n    var diff: String?\n    var newPath: String?\n    var oldPath: String?\n    var aMode: String?\n    var bMode: String?\n    var newFile: Bool?\n    var renamedFile: Bool?\n    var deletedFile: Bool?\n\n    enum CodingKeys: String, CodingKey {\n        case diff\n        case newPath = \"new_path\"\n        case oldPath = \"old_path\"\n        case aMode = \"a_mode\"\n        case bMode = \"b_mode\"\n        case newFile = \"new_file\"\n        case renamedFile = \"renamed_file\"\n        case deletedFile = \"deleted_file\"\n    }\n}\n\nclass GitLabCommitComment: Codable {\n    var note: String?\n    var author: GitLabUser?\n\n    enum CodingKeys: String, CodingKey {\n        case note\n        case author\n    }\n}\n\nclass GitLabCommitStatus: Codable {\n    var status: String?\n    var createdAt: Date?\n    var startedAt: Date?\n    var name: String?\n    var allowFailure: Bool?\n    var author: GitLabUser?\n    var statusDescription: String?\n    var sha: String?\n    var targetURL: URL?\n    var finishedAt: Date?\n    var id: Int?\n    var ref: String?\n\n    enum CodingKeys: String, CodingKey {\n        case status\n        case createdAt = \"created_at\"\n        case startedAt = \"started_at\"\n        case name\n        case allowFailure = \"allow_failure\"\n        case author\n        case statusDescription = \"description\"\n        case sha\n        case targetURL = \"target_url\"\n        case finishedAt = \"finished_at\"\n        case id\n        case ref\n    }\n}\n\nextension GitLabAccount {\n\n    /**\n     Get a list of repository commits in a project.\n     - parameter id: The ID of a project or namespace/project name owned by the authenticated user.\n     - parameter refName: The name of a repository branch or tag or if not given the default branch.\n     - parameter since: Only commits after or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ.\n     - parameter until: Only commits before or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ.\n     - parameter completion: Callback for the outcome of the fetch.\n     */\n    func commits(\n        _ session: GitURLSession = URLSession.shared,\n        id: String,\n        refName: String = \"\",\n        since: String = \"\",\n        until: String = \"\",\n        completion: @escaping (_ response: Result<GitLabCommit, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitLabCommitRouter.readCommits(\n            self.configuration,\n            id: id,\n            refName: refName,\n            since: since,\n            until: until\n        )\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: GitLabCommit.self\n        ) { json, error in\n\n            if let error {\n                completion(Result.failure(error))\n            }\n\n            if let json {\n                completion(Result.success(json))\n            }\n        }\n    }\n\n    /**\n     Get a specific commit in a project.\n     - parameter id: The ID of a project or namespace/project name owned by the authenticated user.\n     - parameter sha: The commit hash or name of a repository branch or tag.\n     - parameter completion: Callback for the outcome of the fetch.\n     */\n    func commit(\n        _ session: GitURLSession = URLSession.shared,\n        id: String,\n        sha: String,\n        completion: @escaping (_ response: Result<GitLabCommit, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitLabCommitRouter.readCommit(self.configuration, id: id, sha: sha)\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: GitLabCommit.self\n        ) { json, error in\n\n            if let error {\n                completion(Result.failure(error))\n            }\n\n            if let json {\n                completion(Result.success(json))\n            }\n        }\n    }\n\n    /**\n     Get a diff of a commit in a project.\n     - parameter id: The ID of a project or namespace/project name owned by the authenticated user.\n     - parameter sha: The commit hash or name of a repository branch or tag.\n     - parameter completion: Callback for the outcome of the fetch.\n     */\n    func commitDiffs(\n        _ session: GitURLSession = URLSession.shared,\n        id: String,\n        sha: String,\n        completion: @escaping (_ response: Result<GitLabCommitDiff, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitLabCommitRouter.readCommitDiffs(self.configuration, id: id, sha: sha)\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: GitLabCommitDiff.self\n        ) { json, error in\n\n            if let error {\n                completion(Result.failure(error))\n            }\n\n            if let json {\n                completion(Result.success(json))\n            }\n        }\n    }\n\n    /**\n     Get the comments of a commit in a project.\n     - parameter id: The ID of a project or namespace/project name owned by the authenticated user.\n     - parameter sha: The commit hash or name of a repository branch or tag.\n     - parameter completion: Callback for the outcome of the fetch.\n     */\n    func commitComments(\n        _ session: GitURLSession = URLSession.shared,\n        id: String,\n        sha: String,\n        completion: @escaping (_ response: Result<GitLabCommitComment, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitLabCommitRouter.readCommitComments(self.configuration, id: id, sha: sha)\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: GitLabCommitComment.self\n        ) { json, error in\n\n            if let error {\n                completion(Result.failure(error))\n            }\n\n            if let json {\n                completion(Result.success(json))\n            }\n        }\n    }\n\n    /**\n     Get the statuses of a commit in a project.\n     - parameter id: The ID of a project or namespace/project name owned by the authenticated user.\n     - parameter sha: The commit hash or name of a repository branch or tag.\n     - parameter ref: The name of a repository branch or tag or, if not given, the default branch.\n     - parameter stage: Filter by build stage, e.g. `test`.\n     - parameter name: Filter by job name, e.g. `bundler:audit`.\n     - parameter all: Return all statuses, not only the latest ones. (Boolean value)\n     - parameter completion: Callback for the outcome of the fetch.\n     */\n    func commitStatuses(\n        _ session: GitURLSession = URLSession.shared,\n        id: String,\n        sha: String,\n        ref: String = \"\",\n        stage: String = \"\",\n        name: String = \"\",\n        all: Bool = false,\n        completion: @escaping (_ response: Result<GitLabCommitStatus, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitLabCommitRouter.readCommitStatuses(\n            self.configuration, id: id,\n            sha: sha,\n            ref: ref,\n            stage: stage,\n            name: name,\n            all: all\n        )\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: GitLabCommitStatus.self\n        ) { json, error in\n\n            if let error {\n                completion(Result.failure(error))\n            }\n\n            if let json {\n                completion(Result.success(json))\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitLab/Model/GitLabEvent.swift",
    "content": "//\n//  GitLabEvent.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nclass GitLabEvent: Codable {\n    var title: String?\n    var projectID: Int?\n    var actionName: String?\n    var targetID: Int?\n    var targetType: String?\n    var authorID: Int?\n    var data: GitLabEventData?\n    var targetTitle: String?\n    var author: GitLabUser?\n    var authorUsername: String?\n    var createdAt: Date?\n    var note: GitLabEventNote?\n\n    enum CodingKeys: String, CodingKey {\n        case title\n        case projectID = \"project_id\"\n        case actionName = \"action_name\"\n        case targetID = \"target_id\"\n        case targetType = \"target_type\"\n        case authorID = \"author_id\"\n        case data\n        case targetTitle = \"target_title\"\n        case author\n        case authorUsername = \"author_username\"\n        case createdAt = \"created_at\"\n        case note\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitLab/Model/GitLabEventData.swift",
    "content": "//\n//  GitLabEventData.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nclass GitLabEventData: Codable {\n    var objectKind: String?\n    var eventName: String?\n    var before: String?\n    var after: String?\n    var ref: String?\n    var checkoutSha: String?\n    var message: String?\n    var userID: Int?\n    var userName: String?\n    var userEmail: String?\n    var userAvatar: URL?\n    var projectID: Int?\n    var project: GitLabProject?\n    var commits: [GitLabCommit]?\n    var totalCommitsCount: Int?\n\n    enum CodingKeys: String, CodingKey {\n        case objectKind = \"object_kind\"\n        case eventName = \"event_name\"\n        case before\n        case after\n        case ref\n        case checkoutSha = \"checkout_sha\"\n        case message\n        case userID = \"user_id\"\n        case userName = \"user_name\"\n        case userEmail = \"user_email\"\n        case userAvatar = \"user_avater\"\n        case projectID = \"project_id\"\n        case project\n        case commits\n        case totalCommitsCount = \"total_commits_count\"\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitLab/Model/GitLabEventNote.swift",
    "content": "//\n//  GitLabEventNote.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nclass GitLabEventNote: Codable {\n    var id: Int?\n    var body: String?\n    var attachment: String?\n    var author: GitLabUser?\n    var createdAt: Date?\n    var system: Bool?\n    var upvote: Bool?\n    var downvote: Bool?\n    var notableID: Int?\n    var notableType: String?\n\n    init(_ json: [String: AnyObject]) {\n        id = json[\"id\"] as? Int\n        body = json[\"body\"] as? String\n        attachment = json[\"attachment\"] as? String\n        author = GitLabUser(json[\"author\"] as? [String: AnyObject] ?? [:])\n        createdAt = GitTime.rfc3339Date(json[\"created_at\"] as? String)\n        system = json[\"system\"] as? Bool\n        upvote = json[\"upvote\"] as? Bool\n        downvote = json[\"downvote\"] as? Bool\n        notableID = json[\"notable_id\"] as? Int\n        notableType = json[\"notable_type\"] as? String\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitLab/Model/GitLabGroupAccess.swift",
    "content": "//\n//  GitLabGroupAccess.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nclass GitLabGroupAccess: Codable {\n    var accessLevel: Int?\n    var notificationLevel: Int?\n\n    init(_ json: [String: AnyObject]) {\n        accessLevel = json[\"access_level\"] as? Int\n        notificationLevel = json[\"notification_level\"] as? Int\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitLab/Model/GitLabNamespace.swift",
    "content": "//\n//  GitLabNamespace.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nclass GitLabNamespace: Codable {\n    var id: Int?\n    var name: String?\n    var path: String?\n    var ownerID: Int?\n    var createdAt: Date?\n    var updatedAt: Date?\n    var namespaceDescription: String?\n    var avatar: GitLabAvatarURL?\n    var shareWithGroupLocked: Bool?\n    var visibilityLevel: Int?\n    var requestAccessEnabled: Bool?\n    var deletedAt: Date?\n    var lfsEnabled: Bool?\n\n    init(_ json: [String: AnyObject]) {\n        if let id = json[\"id\"] as? Int {\n            self.id = id\n            name = json[\"name\"] as? String\n            path = json[\"path\"] as? String\n            ownerID = json[\"owner_id\"] as? Int\n            createdAt = GitTime.rfc3339Date(json[\"created_at\"] as? String)\n            updatedAt = GitTime.rfc3339Date(json[\"updated_at\"] as? String)\n            namespaceDescription = json[\"description\"] as? String\n            avatar = GitLabAvatarURL(json[\"avatar\"] as? [String: AnyObject] ?? [:])\n            shareWithGroupLocked = json[\"share_with_group_lock\"] as? Bool\n            visibilityLevel = json[\"visibility_level\"] as? Int\n            requestAccessEnabled = json[\"request_access_enabled\"] as? Bool\n            deletedAt = GitTime.rfc3339Date(json[\"deleted_at\"] as? String)\n            lfsEnabled = json[\"lfs_enabled\"] as? Bool\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitLab/Model/GitLabPermissions.swift",
    "content": "//\n//  Permissions.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nclass GitLabPermissions: Codable {\n    var projectAccess: GitLabProjectAccess?\n    var groupAccess: GitLabGroupAccess?\n\n    init(_ json: [String: AnyObject]) {\n        projectAccess = GitLabProjectAccess(json[\"project_access\"] as? [String: AnyObject] ?? [:])\n        groupAccess = GitLabGroupAccess(json[\"group_access\"] as? [String: AnyObject] ?? [:])\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitLab/Model/GitLabProject.swift",
    "content": "//\n//  GitLabProject.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nenum GitLabVisibilityLevel: Int {\n    case `private` = 0\n    case `internal` = 10\n    case `public` = 20\n}\n\nclass GitLabProject: Codable {\n    let id: Int\n    let owner: GitLabUser\n    var name: String?\n    var nameWithNamespace: String?\n    var isPrivate: Bool?\n    var projectDescription: String?\n    var sshURL: URL?\n    var cloneURL: URL?\n    var webURL: URL?\n    var path: String?\n    var pathWithNamespace: String?\n    var containerRegistryEnabled: Bool?\n    var defaultBranch: String?\n    var tagList: [String]?\n    var isArchived: Bool?\n    var issuesEnabled: Bool?\n    var mergeRequestsEnabled: Bool?\n    var wikiEnabled: Bool?\n    var buildsEnabled: Bool?\n    var snippetsEnabled: Bool?\n    var sharedRunnersEnabled: Bool?\n    var creatorID: Int?\n    var namespace: GitLabNamespace?\n    var avatarURL: URL?\n    var starCount: Int?\n    var forksCount: Int?\n    var openIssuesCount: Int?\n    var runnersToken: String?\n    var publicBuilds: Bool?\n    var createdAt: Date?\n    var lastActivityAt: Date?\n    var lfsEnabled: Bool?\n    var visibilityLevel: String?\n    var onlyAllowMergeIfBuildSucceeds: Bool?\n    var requestAccessEnabled: Bool?\n    var permissions: String?\n\n    enum CodingKeys: String, CodingKey {\n        case id\n        case owner\n        case name\n        case nameWithNamespace = \"name_with_namespace\"\n        case isPrivate = \"public\"\n        case projectDescription = \"description\"\n        case sshURL = \"ssh_url_to_repo\"\n        case cloneURL = \"http_url_to_repo\"\n        case webURL = \"web_url\"\n        case path\n        case pathWithNamespace = \"path_with_namespace\"\n        case containerRegistryEnabled = \"container_registry_enabled\"\n        case defaultBranch = \"default_branch\"\n        case tagList = \"tag_list\"\n        case isArchived = \"archived\"\n        case issuesEnabled = \"issues_enabled\"\n        case mergeRequestsEnabled = \"merge_requests_enabled\"\n        case wikiEnabled = \"wiki_enabled\"\n        case buildsEnabled = \"builds_enabled\"\n        case snippetsEnabled = \"snippets_enabled\"\n        case sharedRunnersEnabled = \"shared_runners_enabled\"\n        case publicBuilds = \"public_builds\"\n        case creatorID = \"creator_id\"\n        case namespace\n        case avatarURL = \"avatar_url\"\n        case starCount = \"star_count\"\n        case forksCount = \"forks_count\"\n        case openIssuesCount = \"open_issues_count\"\n        case visibilityLevel = \"visibility_level\"\n        case createdAt = \"created_at\"\n        case lastActivityAt = \"last_activity_at\"\n        case lfsEnabled = \"lfs_enabled\"\n        case runnersToken = \"runners_token\"\n        case onlyAllowMergeIfBuildSucceeds = \"only_allow_merge_if_build_succeeds\"\n        case requestAccessEnabled = \"request_access_enabled\"\n        case permissions = \"permissions\"\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitLab/Model/GitLabProjectAccess.swift",
    "content": "//\n//  GitLabProjectAccess.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nclass GitLabProjectAccess: Codable {\n    var accessLevel: Int?\n    var notificationLevel: Int?\n\n    init(_ json: [String: AnyObject]) {\n        accessLevel = json[\"access_level\"] as? Int\n        notificationLevel = json[\"notification_level\"] as? Int\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitLab/Model/GitLabProjectHook.swift",
    "content": "//\n//  GitLabProjectHook.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nclass GitLabProjectHook: Codable {\n    var id: Int?\n    var url: URL?\n    var projectID: Int?\n    var pushEvents: Bool?\n    var issuesEvents: Bool?\n    var mergeRequestsEvents: Bool?\n    var tagPushEvents: Bool?\n    var noteEvents: Bool?\n    var buildEvents: Bool?\n    var pipelineEvents: Bool?\n    var wikiPageEvents: Bool?\n    var enableSSLVerification: Bool?\n    var createdAt: Date?\n\n    enum CodingKeys: String, CodingKey {\n        case id\n        case url\n        case projectID = \"project_id\"\n        case pushEvents = \"push_events\"\n        case issuesEvents = \"issues_events\"\n        case mergeRequestsEvents = \"merge_requests_events\"\n        case tagPushEvents = \"tag_push_events\"\n        case noteEvents = \"note_events\"\n        case buildEvents = \"build_events\"\n        case pipelineEvents = \"pipeline_events\"\n        case wikiPageEvents = \"wiki_page_events\"\n        case enableSSLVerification = \"enable_ssl_verification\"\n        case createdAt = \"created_at\"\n    }\n}\n\nextension GitLabAccount {\n\n    /**\n     Get a list of project hooks.\n     - parameter id: The ID of the project or namespace/project name.\n     Make sure that the namespace/project-name is URL-encoded, eg. \"%2F\" for \"/\".\n     - parameter completion: Callback for the outcome of the fetch.\n     */\n    func projectHooks(\n        _ session: GitURLSession = URLSession.shared,\n        id: String,\n        completion: @escaping (_ response: Result<GitLabProjectHook, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitLabProjectRouter.readProjectHooks(configuration: configuration, id: id)\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: GitLabProjectHook.self\n        ) { json, error in\n\n            if let error {\n                completion(Result.failure(error))\n            }\n\n            if let json {\n                completion(Result.success(json))\n            }\n        }\n    }\n\n    /**\n     Get a specific hook from a project.\n     - parameter id: The ID of the project or namespace/project name.\n     Make sure that the namespace/project-name is URL-encoded, eg. \"%2F\" for \"/\".\n     - parameter hookId: The ID of the hook in the project\n     (you can get the ID of a hook by searching for it with the **allProjectHooks** request).\n     - parameter completion: Callback for the outcome of the fetch.\n     */\n    func projectHook(\n        _ session: GitURLSession = URLSession.shared,\n        id: String,\n        hookId: String,\n        completion: @escaping (_ response: Result<GitLabProjectHook, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitLabProjectRouter.readProjectHook(\n            configuration: configuration,\n            id: id,\n            hookId: hookId\n        )\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: GitLabProjectHook.self\n        ) { json, error in\n\n            if let error {\n                completion(Result.failure(error))\n            }\n\n            if let json {\n                completion(Result.success(json))\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitLab/Model/GitLabUser.swift",
    "content": "//\n//  GitLabUser.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nclass GitLabUser: Codable {\n    var id: Int\n    var username: String?\n    var state: String?\n    var avatarURL: URL?\n    var webURL: URL?\n    var createdAt: Date?\n    var isAdmin: Bool?\n    var name: String?\n    var lastSignInAt: Date?\n    var confirmedAt: Date?\n    var email: String?\n    var projectsLimit: Int?\n    var currentSignInAt: Date?\n    var canCreateGroup: Bool?\n    var canCreateProject: Bool?\n    var twoFactorEnabled: Bool?\n    var external: Bool?\n\n    init(_ json: [String: Any]) {\n        if let id = json[\"id\"] as? Int {\n            name = json[\"name\"] as? String\n            username = json[\"username\"] as? String\n            self.id = id\n            state = json[\"state\"] as? String\n            if let urlString = json[\"avatar_url\"] as? String, let url = URL(string: urlString) {\n                avatarURL = url\n            }\n            if let urlString = json[\"web_url\"] as? String, let url = URL(string: urlString) {\n                webURL = url\n            }\n            createdAt = GitTime.rfc3339Date(json[\"created_at\"] as? String)\n            isAdmin = json[\"is_admin\"] as? Bool\n            lastSignInAt = GitTime.rfc3339Date(json[\"last_sign_in_at\"] as? String)\n            confirmedAt = GitTime.rfc3339Date(json[\"confirmed_at\"] as? String)\n            email = json[\"email\"] as? String\n            projectsLimit = json[\"projects_limit\"] as? Int\n            currentSignInAt = GitTime.rfc3339Date(json[\"current_sign_in_at\"] as? String)\n            canCreateGroup = json[\"can_create_group\"] as? Bool\n            canCreateProject = json[\"can_create_project\"] as? Bool\n            twoFactorEnabled = json[\"two_factor_enabled\"] as? Bool\n            external = json[\"external\"] as? Bool\n        } else {\n            id = -1\n        }\n    }\n}\n\nextension GitLabAccount {\n\n    /**\n     Fetches the currently logged in user\n     - parameter completion: Callback for the outcome of the fetch.\n     */\n    @discardableResult\n    func me(\n        _ session: GitURLSession = URLSession.shared,\n        completion: @escaping (_ response: Result<GitLabUser, Error>) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n        let router = GitLabUserRouter.readAuthenticatedUser(self.configuration)\n\n        return router.load(\n            session,\n            dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter),\n            expectedResultType: GitLabUser.self\n        ) { data, error in\n\n            if let error {\n                completion(Result.failure(error))\n            }\n\n            if let data {\n                completion(Result.success(data))\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitLab/Routers/GitLabCommitRouter.swift",
    "content": "//\n//  GitLabCommitRouter.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nenum GitLabCommitRouter: GitRouter {\n    case readCommits(GitRouterConfiguration, id: String, refName: String, since: String, until: String)\n    case readCommit(GitRouterConfiguration, id: String, sha: String)\n    case readCommitDiffs(GitRouterConfiguration, id: String, sha: String)\n    case readCommitComments(GitRouterConfiguration, id: String, sha: String)\n    case readCommitStatuses(\n        GitRouterConfiguration,\n        id: String,\n        sha: String,\n        ref: String,\n        stage: String,\n        name: String,\n        all: Bool\n    )\n\n    var configuration: GitRouterConfiguration? {\n        switch self {\n        case let .readCommits(config, _, _, _, _): return config\n        case let .readCommit(config, _, _): return config\n        case let .readCommitDiffs(config, _, _): return config\n        case let .readCommitComments(config, _, _): return config\n        case let .readCommitStatuses(config, _, _, _, _, _, _): return config\n        }\n    }\n\n    var method: GitHTTPMethod {\n        .GET\n    }\n\n    var encoding: GitHTTPEncoding {\n        .url\n    }\n\n    var params: [String: Any] {\n        switch self {\n        case let .readCommits(_, _, refName, since, until):\n            return [\"ref_name\": refName, \"since\": since, \"until\": until]\n        case .readCommit:\n            return [:]\n        case .readCommitDiffs:\n            return [:]\n        case .readCommitComments:\n            return [:]\n        case let .readCommitStatuses(_, _, _, ref, stage, name, all):\n            return [\"ref\": ref, \"stage\": stage, \"name\": name, \"all\": String(all)]\n        }\n    }\n\n    var path: String {\n        switch self {\n        case let .readCommits(_, id, _, _, _):\n            return \"project/\\(id)/repository/commits\"\n        case let .readCommit(_, id, sha):\n            return \"project/\\(id)/repository/commits/\\(sha)\"\n        case let .readCommitDiffs(_, id, sha):\n            return \"project/\\(id)/repository/commits/\\(sha)/diff\"\n        case let .readCommitComments(_, id, sha):\n            return \"project/\\(id)/repository/commits/\\(sha)/comments\"\n        case let .readCommitStatuses(_, id, sha, _, _, _, _):\n            return \"project/\\(id)/repository/commits/\\(sha)/statuses\"\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitLab/Routers/GitLabOAuthRouter.swift",
    "content": "//\n//  GitLabOAuthRouter.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nenum GitLabOAuthRouter: GitRouter {\n    case authorize(GitLabOAuthConfiguration, String)\n    case accessToken(GitLabOAuthConfiguration, String, String)\n\n    var configuration: GitRouterConfiguration? {\n        switch self {\n        case .authorize(let config, _): return config\n        case .accessToken(let config, _, _): return config\n        }\n    }\n\n    var method: GitHTTPMethod {\n        switch self {\n        case .authorize:\n            return .GET\n        case .accessToken:\n            return .POST\n        }\n    }\n\n    var encoding: GitHTTPEncoding {\n        switch self {\n        case .authorize:\n            return .url\n        case .accessToken:\n            return .form\n        }\n    }\n\n    var path: String {\n        switch self {\n        case .authorize:\n            return \"oauth/authorize\"\n        case .accessToken:\n            return \"oauth/token\"\n        }\n    }\n\n    var params: [String: Any] {\n        switch self {\n        case let .authorize(config, redirectURI):\n            return [\n                \"client_id\": config.token as AnyObject,\n                \"response_type\": \"code\" as AnyObject,\n                \"redirect_uri\": redirectURI as AnyObject]\n        case let .accessToken(config, code, rediredtURI):\n            return [\n                \"client_id\": config.token as AnyObject,\n                \"client_secret\": config.secret as AnyObject,\n                \"code\": code as AnyObject, \"grant_type\":\n                    \"authorization_code\" as AnyObject,\n                \"redirect_uri\": rediredtURI as AnyObject]\n        }\n    }\n\n    var URLRequest: Foundation.URLRequest? {\n        switch self {\n        case .authorize(let config, _):\n            let url = URL(string: path, relativeTo: URL(string: config.webEndpoint!)!)\n            let components = URLComponents(url: url!, resolvingAgainstBaseURL: true)\n            return request(components!, parameters: params)\n        case .accessToken(let config, _, _):\n            let url = URL(string: path, relativeTo: URL(string: config.webEndpoint!)!)\n            let components = URLComponents(url: url!, resolvingAgainstBaseURL: true)\n            return request(components!, parameters: params)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitLab/Routers/GitLabProjectRouter.swift",
    "content": "//\n//  GitLabProjectRouter.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nenum GitLabVisibility: String {\n    case visbilityPublic = \"public\"\n    case visibilityInternal = \"interal\"\n    case visibilityPrivate = \"private\"\n    case all = \"\"\n}\n\nenum GitLabOrderBy: String {\n    case id = \"id\"\n    case name = \"name\"\n    case path = \"path\"\n    case creationDate = \"created_at\"\n    case updateDate = \"updated_at\"\n    case lastActvityDate = \"last_activity_at\"\n}\n\nenum GitLabSort: String {\n    case ascending = \"asc\"\n    case descending = \"desc\"\n}\n\nenum GitLabProjectRouter: GitRouter {\n    case readAuthenticatedProjects(\n        configuration: GitRouterConfiguration,\n        page: String,\n        perPage: String,\n        archived: Bool,\n        visibility: GitLabVisibility,\n        orderBy: GitLabOrderBy,\n        sort: GitLabSort,\n        search: String,\n        simple: Bool)\n    case readVisibleProjects(\n        configuration: GitRouterConfiguration,\n        page: String,\n        perPage: String,\n        archived: Bool,\n        visibility: GitLabVisibility,\n        orderBy: GitLabOrderBy,\n        sort: GitLabSort,\n        search: String,\n        simple: Bool)\n    case readOwnedProjects(\n        configuration: GitRouterConfiguration,\n        page: String,\n        perPage: String,\n        archived: Bool,\n        visibility: GitLabVisibility,\n        orderBy: GitLabOrderBy,\n        sort: GitLabSort,\n        search: String,\n        simple: Bool)\n    case readStarredProjects(\n        configuration: GitRouterConfiguration,\n        page: String,\n        perPage: String,\n        archived: Bool,\n        visibility: GitLabVisibility,\n        orderBy: GitLabOrderBy,\n        sort: GitLabSort,\n        search: String,\n        simple: Bool)\n    case readAllProjects(\n        configuration: GitRouterConfiguration,\n        page: String,\n        perPage: String,\n        archived: Bool,\n        visibility: GitLabVisibility,\n        orderBy: GitLabOrderBy,\n        sort: GitLabSort,\n        search: String,\n        simple: Bool)\n    case readSingleProject(configuration: GitRouterConfiguration, id: String)\n    case readProjectEvents(configuration: GitRouterConfiguration, id: String, page: String, perPage: String)\n    case readProjectHooks(configuration: GitRouterConfiguration, id: String)\n    case readProjectHook(configuration: GitRouterConfiguration, id: String, hookId: String)\n\n    var configuration: GitRouterConfiguration? {\n        switch self {\n        case .readAuthenticatedProjects(let config, _, _, _, _, _, _, _, _): return config\n        case .readVisibleProjects(let config, _, _, _, _, _, _, _, _): return config\n        case .readOwnedProjects(let config, _, _, _, _, _, _, _, _): return config\n        case .readStarredProjects(let config, _, _, _, _, _, _, _, _): return config\n        case .readAllProjects(let config, _, _, _, _, _, _, _, _): return config\n        case .readSingleProject(let config, _): return config\n        case .readProjectEvents(let config, _, _, _): return config\n        case .readProjectHooks(let config, _): return config\n        case .readProjectHook(let config, _, _): return config\n        }\n    }\n\n    var method: GitHTTPMethod {\n        .GET\n    }\n\n    var encoding: GitHTTPEncoding {\n        .url\n    }\n\n    var params: [String: Any] {\n        switch self {\n        case let .readAuthenticatedProjects(\n            _,\n            page,\n            perPage,\n            archived,\n            visibility,\n            orderBy,\n            sort,\n            search,\n            simple\n        ):\n            return [\n                \"page\": page,\n                \"per_page\": perPage,\n                \"archived\": String(archived),\n                \"visibility\": visibility,\n                \"order_by\": orderBy,\n                \"sort\": sort,\n                \"search\": search,\n                \"simple\": String(simple)\n            ]\n        case let .readVisibleProjects(\n            _,\n            page,\n            perPage,\n            archived,\n            visibility,\n            orderBy,\n            sort,\n            search,\n            simple\n        ):\n            return [\n                \"page\": page,\n                \"per_page\": perPage,\n                \"archived\": String(archived),\n                \"visibility\": visibility,\n                \"order_by\": orderBy,\n                \"sort\": sort,\n                \"search\": search,\n                \"simple\": String(simple)\n            ]\n        case let .readOwnedProjects(\n            _,\n            page,\n            perPage,\n            archived,\n            visibility,\n            orderBy,\n            sort,\n            search,\n            simple\n        ):\n            return [\n                \"page\": page,\n                \"per_page\": perPage,\n                \"archived\": String(archived),\n                \"visibility\": visibility,\n                \"order_by\": orderBy,\n                \"sort\": sort,\n                \"search\": search,\n                \"simple\": String(simple)\n            ]\n        case let .readStarredProjects(\n            _,\n            page,\n            perPage,\n            archived,\n            visibility,\n            orderBy,\n            sort,\n            search,\n            simple\n        ):\n            return [\n                \"page\": page,\n                \"per_page\": perPage,\n                \"archived\": String(archived),\n                \"visibility\": visibility,\n                \"order_by\": orderBy,\n                \"sort\": sort,\n                \"search\": search,\n                \"simple\": String(simple)\n            ]\n        case let .readAllProjects(\n            _,\n            page,\n            perPage,\n            archived,\n            visibility,\n            orderBy,\n            sort,\n            search,\n            simple\n        ):\n            return [\n                \"page\": page,\n                \"per_page\": perPage,\n                \"archived\": String(archived),\n                \"visibility\": visibility,\n                \"order_by\": orderBy,\n                \"sort\": sort,\n                \"search\": search,\n                \"simple\": String(simple)\n            ]\n        case .readSingleProject:\n            return [:]\n        case let .readProjectEvents(_, _, page, perPage):\n            return [\"per_page\": perPage, \"page\": page]\n        case .readProjectHooks:\n            return [:]\n        case .readProjectHook:\n            return [:]\n        }\n    }\n\n    var path: String {\n        switch self {\n        case .readAuthenticatedProjects:\n            return \"projects\"\n        case .readVisibleProjects:\n            return \"projects/visible\"\n        case .readOwnedProjects:\n            return \"projects/owned\"\n        case .readStarredProjects:\n            return \"projects/starred\"\n        case .readAllProjects:\n            return \"projects/all\"\n        case .readSingleProject(_, let id):\n            return \"projects/\\(id)\"\n        case .readProjectEvents(_, let id, _, _):\n            return \"projects/\\(id)/events\"\n        case .readProjectHooks(_, let id):\n            return \"projects/\\(id)/hooks\"\n        case let .readProjectHook(_, id, hookId):\n            return \"projects/\\(id)/hooks/\\(hookId)\"\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/GitLab/Routers/GitLabUserRouter.swift",
    "content": "//\n//  GitLabUserRouter.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nenum GitLabUserRouter: GitRouter {\n    case readAuthenticatedUser(GitRouterConfiguration)\n\n    var configuration: GitRouterConfiguration? {\n        switch self {\n        case .readAuthenticatedUser(let config): return config\n        }\n    }\n\n    var method: GitHTTPMethod {\n        .GET\n    }\n\n    var encoding: GitHTTPEncoding {\n        .url\n    }\n\n    var path: String {\n        switch self {\n        case .readAuthenticatedUser:\n            return \"user\"\n        }\n    }\n\n    var params: [String: Any] {\n        [:]\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/Networking/GitJSONPostRouter.swift",
    "content": "//\n//  GitJSONPostRouter.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n// This file should be strictly just be used for Accounts since it's not\n// built for any other networking except those of git accounts\n\nimport Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\n// TODO: DOCS (Nanashi Li)\nprotocol GitJSONPostRouter: GitRouter {\n\n    func postJSON<T>(\n        _ session: GitURLSession,\n        expectedResultType: T.Type,\n        completion: @escaping (_ json: T?, _ error: Error?) -> Void\n    ) -> GitURLSessionDataTaskProtocol?\n\n    func post<T: Codable>(\n        _ session: GitURLSession,\n        decoder: JSONDecoder,\n        expectedResultType: T.Type,\n        completion: @escaping (_ json: T?, _ error: Error?) -> Void\n    ) -> GitURLSessionDataTaskProtocol?\n\n    #if !canImport(FoundationNetworking)\n    @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)\n    func postJSON<T>(_ session: GitURLSession, expectedResultType: T.Type) async throws -> T?\n\n    @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)\n    func post<T: Codable>(\n        _ session: GitURLSession,\n        decoder: JSONDecoder,\n        expectedResultType: T.Type\n    ) async throws -> T\n    #endif\n}\n\nextension GitJSONPostRouter {\n    func postJSON<T>(\n        _ session: GitURLSession = URLSession.shared,\n        expectedResultType _: T.Type,\n        completion: @escaping (_ json: T?, _ error: Error?) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n\n        guard let request = request() else {\n            return nil\n        }\n\n        let data: Data\n\n        do {\n            data = try JSONSerialization.data(withJSONObject: params, options: JSONSerialization.WritingOptions())\n        } catch {\n            completion(nil, error)\n            return nil\n        }\n\n        let task = session.uploadTask(with: request, fromData: data) { data, response, error in\n            if let response = response as? HTTPURLResponse {\n                if !response.wasSuccessful {\n                    var userInfo = [String: Any]()\n                    if let data, let json = try? JSONSerialization.jsonObject(\n                        with: data,\n                        options: .mutableContainers\n                    ) as? [String: Any] {\n                        userInfo[gitErrorKey] = json as Any?\n                    } else if let data, let string = String(data: data, encoding: .utf8) {\n                        userInfo[gitErrorKey] = string as Any?\n                    }\n\n                    let error = NSError(\n                        domain: self.configuration?.errorDomain ?? \"\",\n                        code: response.statusCode,\n                        userInfo: userInfo\n                    )\n\n                    completion(nil, error)\n                    return\n                }\n            }\n\n            if let error {\n                completion(nil, error)\n            } else {\n                if let data {\n                    do {\n                        let JSON = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? T\n                        completion(JSON, nil)\n                    } catch {\n                        completion(nil, error)\n                    }\n                }\n            }\n        }\n        task.resume()\n        return task\n    }\n\n    #if !canImport(FoundationNetworking)\n    @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)\n    func postJSON<T>(\n        _ session: GitURLSession = URLSession.shared,\n        expectedResultType _: T.Type\n    ) async throws -> T? {\n\n        guard let request = request() else {\n            throw NSError(domain: configuration?.errorDomain ?? \"\", code: -876, userInfo: nil)\n        }\n\n        let data = try JSONSerialization.data(withJSONObject: params, options: JSONSerialization.WritingOptions())\n        let responseTuple = try await session.upload(for: request, from: data, delegate: nil)\n        if let response = responseTuple.1 as? HTTPURLResponse {\n            if !response.wasSuccessful {\n                var userInfo = [String: Any]()\n                if let json = try? JSONSerialization.jsonObject(\n                    with: responseTuple.0,\n                    options: .mutableContainers\n                ) as? [String: Any] {\n                    userInfo[gitErrorKey] = json as Any?\n                } else if let string = String(data: responseTuple.0, encoding: .utf8) {\n                    userInfo[gitErrorKey] = string as Any?\n                }\n                throw NSError(domain: configuration?.errorDomain ?? \"\", code: response.statusCode, userInfo: userInfo)\n            }\n        }\n\n        return try JSONSerialization.jsonObject(with: responseTuple.0, options: .mutableContainers) as? T\n    }\n    #endif\n\n    func post<T: Codable>(\n        _ session: GitURLSession = URLSession.shared,\n        decoder: JSONDecoder = JSONDecoder(),\n        expectedResultType _: T.Type,\n        completion: @escaping (_ json: T?, _ error: Error?) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n\n        guard let request = request() else {\n            return nil\n        }\n\n        let data: Data\n        do {\n            data = try JSONSerialization.data(withJSONObject: params, options: JSONSerialization.WritingOptions())\n        } catch {\n            completion(nil, error)\n            return nil\n        }\n\n        let task = session.uploadTask(with: request, fromData: data) { data, response, error in\n            if let response = response as? HTTPURLResponse, !response.wasSuccessful {\n                var userInfo = [String: Any]()\n                if let data, let json = try? JSONSerialization.jsonObject(\n                    with: data,\n                    options: .mutableContainers\n                ) as? [String: Any] {\n                    userInfo[gitErrorKey] = json as Any?\n                } else if let data, let string = String(data: data, encoding: String.Encoding.utf8) {\n                    userInfo[gitErrorKey] = string as Any?\n                }\n                let error = NSError(\n                    domain: self.configuration?.errorDomain ?? \"\",\n                    code: response.statusCode,\n                    userInfo: userInfo\n                )\n\n                completion(nil, error)\n\n                return\n            }\n\n            if let error {\n                completion(nil, error)\n            } else {\n                if let data {\n                    do {\n                        let decoded = try decoder.decode(T.self, from: data)\n                        completion(decoded, nil)\n                    } catch {\n                        completion(nil, error)\n                    }\n                }\n            }\n        }\n        task.resume()\n        return task\n    }\n\n    #if !canImport(FoundationNetworking)\n    @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)\n    func post<T: Codable>(\n        _ session: GitURLSession,\n        decoder: JSONDecoder = JSONDecoder(),\n        expectedResultType _: T.Type\n    ) async throws -> T {\n\n        guard let request = request() else {\n            throw NSError(domain: configuration?.errorDomain ?? \"\", code: -876, userInfo: nil)\n        }\n\n        let data = try JSONSerialization.data(withJSONObject: params, options: JSONSerialization.WritingOptions())\n        let responseTuple = try await session.upload(for: request, from: data, delegate: nil)\n        if let response = responseTuple.1 as? HTTPURLResponse, response.wasSuccessful == false {\n            var userInfo = [String: Any]()\n            if let json = try? JSONSerialization.jsonObject(\n                with: responseTuple.0,\n                options: .mutableContainers\n            ) as? [String: Any] {\n                userInfo[gitErrorKey] = json as Any?\n            } else if let string = String(data: responseTuple.0, encoding: String.Encoding.utf8) {\n                userInfo[gitErrorKey] = string as Any?\n            }\n            throw NSError(domain: configuration?.errorDomain ?? \"\", code: response.statusCode, userInfo: userInfo)\n        }\n\n        return try decoder.decode(T.self, from: responseTuple.0)\n    }\n    #endif\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/Networking/GitRouter.swift",
    "content": "//\n//  GitRouter.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n// This file should be strictly just be used for Accounts since it's not\n// built for any other networking except those of git accounts\n\nimport Foundation\n\n// TODO: DOCS (Nanashi Li)\nenum GitHTTPMethod: String {\n    case GET, POST, PUT, PATCH, DELETE\n}\n\nenum GitHTTPEncoding: Int {\n    case url, form, json\n}\n\nstruct GitHTTPHeader {\n    var headerField: String\n    var value: String\n}\n\nprotocol GitRouterConfiguration {\n    var apiEndpoint: String? { get }\n    var accessToken: String? { get }\n    var accessTokenFieldName: String? { get }\n    var authorizationHeader: String? { get }\n    var errorDomain: String? { get }\n    var customHeaders: [GitHTTPHeader]? { get }\n}\n\nextension GitRouterConfiguration {\n    var accessTokenFieldName: String? {\n        \"access_token\"\n    }\n\n    var authorizationHeader: String? {\n        nil\n    }\n\n    var errorDomain: String? {\n        \"com.codeedit.models.accounts.networking\"\n    }\n\n    var customHeaders: [GitHTTPHeader]? {\n        nil\n    }\n}\n\nprotocol GitRouter {\n    var method: GitHTTPMethod { get }\n    var path: String { get }\n    var encoding: GitHTTPEncoding { get }\n    var params: [String: Any] { get }\n    var configuration: GitRouterConfiguration? { get }\n\n    func urlQuery(_ parameters: [String: Any]) -> [URLQueryItem]?\n\n    func request(_ urlComponents: URLComponents, parameters: [String: Any]) -> URLRequest?\n\n    func load<T: Codable>(\n        _ session: GitURLSession,\n        dateDecodingStrategy: JSONDecoder.DateDecodingStrategy?,\n        expectedResultType: T.Type,\n        completion: @escaping (_ json: T?, _ error: Error?) -> Void\n    ) -> GitURLSessionDataTaskProtocol?\n\n    func load<T: Codable>(\n        _ session: GitURLSession,\n        decoder: JSONDecoder,\n        expectedResultType: T.Type,\n        completion: @escaping (_ json: T?, _ error: Error?) -> Void\n    ) -> GitURLSessionDataTaskProtocol?\n\n    func request() -> URLRequest?\n}\n\nextension GitRouter {\n\n    var gitErrorKey: String { \"ErrorKey\" }\n\n    func request() -> URLRequest? {\n        let url = URL(string: path, relativeTo: URL(string: configuration?.apiEndpoint ?? \"\")!)\n\n        var parameters = encoding == .json ? [:] : params\n\n        if let accessToken = configuration?.accessToken, configuration?.authorizationHeader == nil {\n            parameters[configuration?.accessTokenFieldName ?? \"\"] = accessToken as Any?\n        }\n\n        let components = URLComponents(url: url!, resolvingAgainstBaseURL: true)\n\n        var urlRequest = request(components!, parameters: parameters)\n\n        if let accessToken = configuration?.accessToken, let tokenType = configuration?.authorizationHeader {\n            urlRequest?.addValue(\"\\(tokenType) \\(accessToken)\", forHTTPHeaderField: \"Authorization\")\n        }\n\n        if let customHeaders = configuration?.customHeaders {\n            customHeaders.forEach { httpHeader in\n                urlRequest?.addValue(httpHeader.value, forHTTPHeaderField: httpHeader.headerField)\n            }\n        }\n\n        return urlRequest\n    }\n\n    /// Due to the complexity of the the urlQuery method we disabled lint for the this method\n    /// only so that it doesn't complain... Note this level of complexity is needed to give us as\n    /// much success rate as possible due to all git providers having different types of url schemes.\n    func urlQuery(_ parameters: [String: Any]) -> [URLQueryItem]? { // swiftlint:disable:this cyclomatic_complexity\n        guard !parameters.isEmpty else { return nil }\n\n        var components: [URLQueryItem] = []\n\n        for key in parameters.keys.sorted(by: <) {\n            guard let value = parameters[key] else { continue }\n\n            switch value {\n            case let value as String:\n                if let escapedValue = value.addingPercentEncoding(\n                    withAllowedCharacters: CharacterSet.URLQueryAllowedCharacterSet()\n                ) {\n                    components.append(URLQueryItem(name: key, value: escapedValue))\n                }\n            case let valueArray as [String]:\n                for (index, item) in valueArray.enumerated() {\n                    if let escapedValue = item.addingPercentEncoding(\n                        withAllowedCharacters: CharacterSet.URLQueryAllowedCharacterSet()\n                    ) {\n                        components.append(URLQueryItem(name: \"\\(key)[\\(index)]\", value: escapedValue))\n                    }\n                }\n            case let valueDict as [String: Any]:\n                for nestedKey in valueDict.keys.sorted(by: <) {\n                    guard let value = valueDict[nestedKey] as? String else { continue }\n                    if let escapedValue = value.addingPercentEncoding(\n                        withAllowedCharacters: CharacterSet.URLQueryAllowedCharacterSet()\n                    ) {\n                        components.append(URLQueryItem(name: \"\\(key)[\\(nestedKey)]\", value: escapedValue))\n                    }\n                }\n            default:\n                print(\"Cannot encode object of type \\(type(of: value))\")\n            }\n        }\n\n        return components\n    }\n\n    func request(_ urlComponents: URLComponents, parameters: [String: Any]) -> URLRequest? {\n\n        var urlComponents = urlComponents\n\n        urlComponents.percentEncodedQuery = urlQuery(parameters)?.map {\n            [$0.name, $0.value ?? \"\"].joined(separator: \"=\")\n        }.joined(separator: \"&\")\n\n        guard let url = urlComponents.url else { return nil }\n\n        switch encoding {\n        case .url, .json:\n            var mutableURLRequest = Foundation.URLRequest(url: url)\n\n            mutableURLRequest.httpMethod = method.rawValue\n\n            return mutableURLRequest\n        case .form:\n            let queryData = urlComponents.percentEncodedQuery?.data(using: String.Encoding.utf8)\n\n            // clear the query items as they go into the body\n            urlComponents.queryItems = nil\n\n            var mutableURLRequest = Foundation.URLRequest(url: urlComponents.url!)\n\n            mutableURLRequest.setValue(\"application/x-www-form-urlencoded\", forHTTPHeaderField: \"content-type\")\n\n            mutableURLRequest.httpBody = queryData\n\n            mutableURLRequest.httpMethod = method.rawValue\n\n            return mutableURLRequest as URLRequest\n        }\n    }\n\n    func load<T: Codable>(\n        _ session: GitURLSession = URLSession.shared,\n        dateDecodingStrategy: JSONDecoder.DateDecodingStrategy?,\n        expectedResultType: T.Type,\n        completion: @escaping (_ json: T?, _ error: Error?) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n\n        let decoder = JSONDecoder()\n\n        if let dateDecodingStrategy {\n            decoder.dateDecodingStrategy = dateDecodingStrategy\n        }\n\n        return load(session, decoder: decoder, expectedResultType: expectedResultType, completion: completion)\n    }\n\n    func load<T: Codable>(\n        _ session: GitURLSession = URLSession.shared,\n        decoder: JSONDecoder = JSONDecoder(), expectedResultType _: T.Type,\n        completion: @escaping (_ json: T?, _ error: Error?) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n\n        guard let request = request() else {\n            return nil\n        }\n\n        let task = session.dataTask(with: request) { data, response, err in\n            if let response = response as? HTTPURLResponse {\n                if response.wasSuccessful == false {\n                    var userInfo = [String: Any]()\n                    if let data, let json = try? JSONSerialization.jsonObject(\n                        with: data,\n                        options: .mutableContainers\n                    ) as? [String: Any] {\n\n                        userInfo[gitErrorKey] = json as Any?\n                    }\n\n                    let error = NSError(\n                        domain: self.configuration?.errorDomain ?? \"\",\n                        code: response.statusCode,\n                        userInfo: userInfo\n                    )\n\n                    completion(nil, error)\n\n                    return\n                }\n            }\n\n            if let err {\n                completion(nil, err)\n            } else {\n                if let data {\n                    do {\n                        let decoded = try decoder.decode(T.self, from: data)\n                        completion(decoded, nil)\n                    } catch {\n                        completion(nil, error)\n                    }\n                }\n            }\n        }\n        task.resume()\n        return task\n    }\n\n    @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)\n    func load<T: Codable>(\n        _ session: GitURLSession = URLSession.shared,\n        decoder: JSONDecoder = JSONDecoder(),\n        expectedResultType _: T.Type\n    ) async throws -> T {\n\n        guard let request = request() else {\n            throw NSError(domain: configuration?.errorDomain ?? \"\", code: -876, userInfo: nil)\n        }\n\n        let responseTuple = try await session.data(for: request, delegate: nil)\n\n        if let response = responseTuple.1 as? HTTPURLResponse {\n            if response.wasSuccessful == false {\n                var userInfo = [String: Any]()\n                if let json = try? JSONSerialization.jsonObject(\n                    with: responseTuple.0,\n                    options: .mutableContainers\n                ) as? [String: Any] {\n\n                    userInfo[gitErrorKey] = json as Any?\n\n                }\n\n                throw NSError(domain: configuration?.errorDomain ?? \"\", code: response.statusCode, userInfo: userInfo)\n            }\n        }\n\n        return try decoder.decode(T.self, from: responseTuple.0)\n    }\n\n    @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)\n    func load<T: Codable>(\n        _ session: GitURLSession = URLSession.shared,\n        dateDecodingStrategy: JSONDecoder.DateDecodingStrategy?,\n        expectedResultType: T.Type\n    ) async throws -> T {\n\n        let decoder = JSONDecoder()\n\n        if let dateDecodingStrategy {\n            decoder.dateDecodingStrategy = dateDecodingStrategy\n        }\n\n        return try await load(session, decoder: decoder, expectedResultType: expectedResultType)\n    }\n\n    func load(\n        _ session: GitURLSession = URLSession.shared,\n        completion: @escaping (_ error: Error?) -> Void\n    ) -> GitURLSessionDataTaskProtocol? {\n\n        guard let request = request() else {\n            return nil\n        }\n\n        let task = session.dataTask(with: request) { data, response, err in\n            if let response = response as? HTTPURLResponse {\n                if response.wasSuccessful == false {\n                    var userInfo = [String: Any]()\n                    if let data, let json = try? JSONSerialization.jsonObject(\n                        with: data,\n                        options: .mutableContainers\n                    ) as? [String: Any] {\n\n                        userInfo[gitErrorKey] = json as Any?\n\n                    }\n\n                    let error = NSError(\n                        domain: self.configuration?.errorDomain ?? \"\",\n                        code: response.statusCode,\n                        userInfo: userInfo\n                    )\n\n                    completion(error)\n\n                    return\n                }\n            }\n\n            completion(err)\n        }\n        task.resume()\n        return task\n    }\n}\n\nprivate extension CharacterSet {\n\n    /// https://github.com/Alamofire/Alamofire/blob/3.5rameterEncoding.swift#L220-L225\n    static func URLQueryAllowedCharacterSet() -> CharacterSet {\n\n        // does not include \"?\" or \"/\" due to RFC 3986 - Section 3.4\n        let generalDelimitersToEncode = \":#[]@\"\n        let subDelimitersToEncode = \"!$&'()*+,;=\"\n\n        var allowedCharacterSet = CharacterSet.urlQueryAllowed\n        allowedCharacterSet.remove(charactersIn: generalDelimitersToEncode + subDelimitersToEncode)\n        return allowedCharacterSet\n    }\n}\n\nextension HTTPURLResponse {\n\n    /// Checks what kind of HTTP response we get from the server\n    var wasSuccessful: Bool {\n        let successRange = 200 ..< 300\n        return successRange.contains(statusCode)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/Networking/GitURLSession.swift",
    "content": "//\n//  Session.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n// This file should be strictly just be used for Accounts since it's not\n// built for any other networking except those of git accounts\n\nimport Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\n// TODO: DOCS (Nanashi Li)\nprotocol GitURLSession {\n\n    func dataTask(\n        with request: URLRequest,\n        completionHandler: @escaping (Data?, URLResponse?, Error?) -> Swift.Void\n    ) -> GitURLSessionDataTaskProtocol\n\n    func uploadTask(\n        with request: URLRequest,\n        fromData bodyData: Data?,\n        completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void\n    ) -> GitURLSessionDataTaskProtocol\n\n#if !canImport(FoundationNetworking)\n    @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)\n    func data(\n        for request: URLRequest,\n        delegate: URLSessionTaskDelegate?\n    ) async throws -> (Data, URLResponse)\n\n    @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)\n    func upload(\n        for request: URLRequest,\n        from bodyData: Data,\n        delegate: URLSessionTaskDelegate?\n    ) async throws -> (Data, URLResponse)\n#endif\n}\n\nprotocol GitURLSessionDataTaskProtocol {\n    func resume()\n}\n\nextension URLSessionDataTask: GitURLSessionDataTaskProtocol {}\n\nextension URLSession: GitURLSession {\n\n    func dataTask(\n        with request: URLRequest,\n        completionHandler: @escaping (Data?, URLResponse?, Error?) -> Swift.Void\n    ) -> GitURLSessionDataTaskProtocol {\n        (dataTask(with: request, completionHandler: completionHandler) as URLSessionDataTask)\n    }\n\n    func uploadTask(\n        with request: URLRequest,\n        fromData bodyData: Data?,\n        completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void\n    ) -> GitURLSessionDataTaskProtocol {\n        uploadTask(with: request, from: bodyData, completionHandler: completionHandler)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/Parameters.swift",
    "content": "//\n//  Parameters.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nenum GitSortDirection: String {\n    case asc\n    case desc\n}\n\nenum GitSortType: String {\n    case created\n    case updated\n    case popularity\n    case longRunning = \"long-running\"\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/Utils/GitTime.swift",
    "content": "//\n//  GitTime.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\n// TODO: DOCS (Nanashi Li)\nenum GitTime {\n\n    /**\n     A date formatter for RFC 3339 style timestamps.\n     Uses POSIX locale and GMT timezone so that date values are parsed as absolutes.\n     - (https://tools.ietf.org/html/rfc3339)\n     - (https://developer.apple.com/library/mac/qa/qa1480/_index.html)\n     */\n    static var rfc3339DateFormatter: DateFormatter = {\n        let formatter = DateFormatter()\n        formatter.dateFormat = \"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'\"\n        formatter.locale = Locale(identifier: \"en_US_POSIX\")\n        formatter.timeZone = TimeZone(secondsFromGMT: 0)\n        return formatter\n    }()\n\n    /**\n     Parses RFC 3339 date strings into NSDate\n     - parameter string: The string representation of the date\n     - returns: An `NSDate` with a successful parse, otherwise `nil`\n     */\n    static func rfc3339Date(_ string: String?) -> Date? {\n        guard let string else { return nil }\n        return GitTime.rfc3339DateFormatter.date(from: string)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/Utils/String+PercentEncoding.swift",
    "content": "//\n//  String+PercentEncoding.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nextension String {\n\n    /// Percent-encodes a string to be URL-safe\n    ///\n    /// See https://useyourloaf.com/blog/how-to-percent-encode-a-url-string/ for more info\n    /// - returns: An optional string, with percent encoding to match RFC3986\n    func stringByAddingPercentEncodingForRFC3986() -> String? {\n        let unreserved = \"-._~/?\"\n        var allowed = CharacterSet.alphanumerics\n        allowed.insert(charactersIn: unreserved)\n        return addingPercentEncoding(withAllowedCharacters: allowed)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Accounts/Utils/String+QueryParameters.swift",
    "content": "//\n//  String+QueryParameters.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\n\nimport Foundation\n\nextension String {\n    var bitbucketQueryParameters: [String: String] {\n        let parametersArray = components(separatedBy: \"&\")\n        var parameters = [String: String]()\n        parametersArray.forEach { parameter in\n            let keyValueArray = parameter.components(separatedBy: \"=\")\n            let (key, value) = (keyValueArray.first, keyValueArray.last)\n            if let key = key?.removingPercentEncoding, let value = value?.removingPercentEncoding {\n                parameters[key] = value\n            }\n        }\n        return parameters\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Client/GitClient+Branches.swift",
    "content": "//\n//  GitClient+Branches.swift\n//  CodeEdit\n//\n//  Created by Albert Vinizhanau on 10/20/23.\n//\n\nimport Foundation\n\nextension GitClient {\n    /// Get branches\n    /// - Parameter remote: If passed, fetches branches for the specified remote\n    /// - Returns: Array of branches\n    func getBranches(remote: String? = nil) async throws -> [GitBranch] {\n        var command = \"branch --format \\\"%(refname:short)|%(refname)|%(upstream:short) %(upstream:track)\\\"\"\n        if remote != nil {\n            command += \" -r\"\n        } else {\n            command += \" -a\"\n        }\n\n        return try await run(command)\n            .components(separatedBy: \"\\n\")\n            .filter { $0 != \"\" && !$0.contains(\"HEAD\") && (remote == nil || $0.starts(with: \"\\(remote ?? \"\")/\")) }\n            .compactMap { line in\n                guard let branchPart = line.components(separatedBy: \" \").first else { return nil }\n                let branchComponents = branchPart.components(separatedBy: \"|\")\n                let name = branchComponents[0]\n                let upstream = branchComponents[safe: 2]\n\n                let trackInfoString = line\n                    .dropFirst(branchPart.count)\n                    .trimmingCharacters(in: .whitespacesWithoutNewlines)\n                let trackInfo = parseBranchTrackInfo(from: trackInfoString)\n\n                return GitBranch(\n                    name: remote != nil ? extractBranchName(from: name, with: remote ?? \"\") : name,\n                    longName: branchComponents[safe: 1] ?? name,\n                    upstream: upstream?.isEmpty == true ? nil : upstream,\n                    ahead: trackInfo.ahead,\n                    behind: trackInfo.behind\n                )\n            }\n    }\n\n    /// Get current branch\n    func getCurrentBranch() async throws -> GitBranch? {\n        let branchName = try await run(\"branch --show-current\").trimmingCharacters(in: .whitespacesAndNewlines)\n        let output = try await run(\n            \"for-each-ref --format=\\\"%(refname)|%(upstream:short) %(upstream:track)\\\" refs/heads/\\(branchName)\"\n        )\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n\n        guard let branchPart = output.components(separatedBy: \" \").first else { return nil }\n        let branchComponents = branchPart.components(separatedBy: \"|\")\n        let upstream = branchComponents[safe: 1]\n\n        let trackInfoString = output\n            .dropFirst(branchPart.count)\n            .trimmingCharacters(in: .whitespacesWithoutNewlines)\n        let trackInfo = parseBranchTrackInfo(from: trackInfoString)\n\n        return .init(\n            name: branchName,\n            longName: branchComponents[0],\n            upstream: upstream?.isEmpty == true ? nil : upstream,\n            ahead: trackInfo.ahead,\n            behind: trackInfo.behind\n        )\n    }\n\n    /// Delete branch\n    func deleteBranch(_ branch: GitBranch) async throws {\n        if !branch.isLocal {\n            return\n        }\n\n        _ = try await run(\"branch -d \\(branch.name)\")\n    }\n\n    /// Rename branch\n    /// - Parameter from: Name of the branch to rename\n    /// - Parameter to: New name for branch\n    func renameBranch(oldName: String, newName: String) async throws {\n        _ = try await run(\"branch -m \\(oldName) \\(newName)\")\n    }\n\n    /// Checkout branch\n    /// - Parameter branch: Branch to checkout\n    func checkoutBranch(_ branch: GitBranch, forceLocal: Bool = false, newName: String? = nil) async throws {\n        var command = \"checkout \"\n\n        let targetName = newName ?? branch.name\n\n        if (branch.isRemote && !forceLocal) || newName != nil {\n            let sourceBranch = branch.isRemote\n                ? branch.longName.replacingOccurrences(of: \"refs/remotes/\", with: \"\")\n                : branch.name\n            command += \"-b \\(targetName) \\(sourceBranch)\"\n        } else {\n            command += targetName\n        }\n\n        do {\n            let output = try await run(command)\n            if !output.contains(\"Switched to branch\") && !output.contains(\"Switched to a new branch\") {\n                throw GitClientError.outputError(output)\n            }\n        } catch {\n            // If branch is remote and command failed because branch already exists\n            // try to switch to local branch\n            if let error = error as? GitClientError,\n               branch.isRemote,\n               error.description.contains(\"already exists\") {\n                try await checkoutBranch(branch, forceLocal: true)\n            } else {\n                logger.error(\"Failed to checkout branch: \\(error)\")\n            }\n        }\n    }\n\n    private func parseBranchTrackInfo(from infoString: String) -> (ahead: Int, behind: Int) {\n        let pattern = \"\\\\[ahead (\\\\d+)(?:, behind (\\\\d+))?\\\\]|\\\\[behind (\\\\d+)\\\\]\"\n        // Create a regular expression object\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {\n            fatalError(\"Invalid regular expression pattern\")\n        }\n        var ahead = 0\n        var behind = 0\n        // Match the input string with the regular expression\n        if let match = regex.firstMatch(\n            in: infoString,\n            options: [],\n            range: NSRange(location: 0, length: infoString.utf16.count)\n        ) {\n            // Extract the captured groups\n            if let aheadRange = Range(match.range(at: 1), in: infoString),\n               let aheadValue = Int(infoString[aheadRange]) {\n                ahead = aheadValue\n            }\n            if let behindRange = Range(match.range(at: 2), in: infoString),\n               let behindValue = Int(infoString[behindRange]) {\n                behind = behindValue\n            }\n            if let behindRange = Range(match.range(at: 3), in: infoString),\n               let behindValue = Int(infoString[behindRange]) {\n                behind = behindValue\n            }\n        }\n        return (ahead, behind)\n    }\n\n    private func extractBranchName(from fullBranchName: String, with remoteName: String) -> String {\n        // Ensure the fullBranchName starts with the remoteName followed by a slash\n        let prefix = \"\\(remoteName)/\"\n        if fullBranchName.hasPrefix(prefix) {\n            // Remove the remoteName and the slash to get the branch name\n            return String(fullBranchName.dropFirst(prefix.count))\n        } else {\n            // If the fullBranchName does not start with the expected remoteName, return it unchanged\n            return fullBranchName\n        }\n    }\n\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Client/GitClient+Clone.swift",
    "content": "//\n//  GitClient+Clone.swift\n//  CodeEdit\n//\n//  Created by Albert Vinizhanau on 10/20/23.\n//\n\nimport Foundation\nimport Combine\n\nextension GitClient {\n    struct CloneProgress {\n        let progress: Double\n        let state: GitCloneProgressState\n    }\n\n    enum GitCloneProgressState {\n        case initialState\n        case counting\n        case compressing\n        case receiving\n        case resolving\n\n        var label: String {\n            switch self {\n            case .initialState: \"Cloning\"\n            case .counting: \"Counting\"\n            case .compressing: \"Compressing\"\n            case .receiving: \"Receiving\"\n            case .resolving: \"Resolving\"\n            }\n        }\n    }\n\n    /// Clone repository\n    /// - Parameters:\n    ///   - remoteUrl: URL of remote repository\n    ///   - localPath: Local path to clone\n    /// - Returns: Stream of progress\n    func cloneRepository(\n        remoteUrl: URL,\n        localPath: URL\n    ) -> AsyncThrowingMapSequence<LiveCommandStream, CloneProgress> {\n        let command = \"clone \\(remoteUrl.absoluteString) \\(localPath.relativePath.escapedDirectory()) --progress\"\n\n        return self.runLive(command)\n            .map { line in\n                // Inspired by VS Code https://github.com/microsoft/vscode/blob/main/extensions/git/src/git.ts\n                // Parsing git clone output (for patterns look at cloneMatchTypes) and calculating total progress\n                // Each step has own base progress and multiplier\n                // Total progress is baseProgress + (progress from output * multiplier)\n                // For example current outout is Counting objects: 10%, baseProgress is 0, multiplier is 0.1\n                // So total progress is 0 + (10 * 0.1) = 1%\n                for cloneMatchType in self.cloneMatchTypes {\n                    if let progress = self.matchAndCalculateProgress(\n                        line,\n                        cloneMatchType.pattern,\n                        baseProgress: cloneMatchType.baseProgress,\n                        multiplier: cloneMatchType.multiplier\n                    ) {\n                        return .init(progress: progress, state: cloneMatchType.state)\n                    }\n                }\n\n                return .init(progress: 0, state: .initialState)\n            }\n    }\n\n    fileprivate struct CloneMatchType {\n        let pattern: String\n        let baseProgress: Double\n        let multiplier: Double\n        let state: GitCloneProgressState\n    }\n\n    fileprivate var cloneMatchTypes: [CloneMatchType] {\n        [\n            .init(pattern: \"Counting objects:\\\\s*(\\\\d+)%\", baseProgress: 0, multiplier: 0.1, state: .counting),\n            .init(pattern: \"Compressing objects:\\\\s*(\\\\d+)%\", baseProgress: 10, multiplier: 0.1, state: .compressing),\n            .init(pattern: \"Receiving objects:\\\\s*(\\\\d+)%\", baseProgress: 20, multiplier: 0.4, state: .receiving),\n            .init(pattern: \"Resolving deltas:\\\\s*(\\\\d+)%\", baseProgress: 60, multiplier: 0.4, state: .resolving),\n        ]\n    }\n\n    /// Match pattern in output line and calculate progress\n    fileprivate func matchAndCalculateProgress(\n        _ line: String,\n        _ pattern: String,\n        baseProgress: Double,\n        multiplier: Double\n    ) -> Double? {\n        let match = try? NSRegularExpression(pattern: pattern, options: .caseInsensitive)\n            .firstMatch(in: line, range: NSRange(line.startIndex..., in: line))\n\n        if let match,\n           let range = Range(match.range(at: 1), in: line),\n           let progress = Int(line[range]) {\n            return baseProgress + Double(progress) * multiplier\n        }\n        return nil\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Client/GitClient+Commit.swift",
    "content": "//\n//  GitClient+Commit.swift\n//  CodeEdit\n//\n//  Created by Albert Vinizhanau on 10/20/23.\n//\n\nimport Foundation\nimport RegexBuilder\n\nextension GitClient {\n    /// Commit files\n    /// - Parameters:\n    ///   - message: Commit message\n    func commit(message: String, details: String?) async throws {\n        let message = message.replacingOccurrences(of: #\"\"\"#, with: #\"\\\"\"#)\n        let command: String\n\n        if let msgDetails = details {\n            command = \"commit --message=\\\"\\(message + (msgDetails.isEmpty ? \"\" : (\"\\n\\n\" + msgDetails)))\\\"\"\n        } else {\n            command = \"commit --message=\\\"\\(message)\\\"\"\n        }\n\n        _ = try await run(command)\n    }\n\n    /// Add file to git\n    /// - Parameter file: File to add\n    func add(_ files: [URL]) async throws {\n        let output = try await run(\"add \\(files.map { \"'\\($0.path(percentEncoded: false))'\" }.joined(separator: \" \"))\")\n        print(output)\n    }\n\n    /// Add file to git\n    /// - Parameter file: File to add\n    func reset(_ files: [URL]) async throws {\n        _ = try await run(\"reset \\(files.map { \"'\\($0.path(percentEncoded: false))'\" }.joined(separator: \" \"))\")\n    }\n\n    /// Returns tuple of unsynced commits both ahead and behind\n    func numberOfUnsyncedCommits() async throws -> (ahead: Int, behind: Int) {\n        let output = try await run(\"status -sb --porcelain=v2\").trimmingCharacters(in: .whitespacesAndNewlines)\n        return try parseUnsyncedCommitsOutput(from: output)\n    }\n\n    func getCommitChangedFiles(commitSHA: String) async throws -> [GitChangedFile] {\n        do {\n            let output = try await run(\"diff-tree --no-commit-id --name-status -r \\(commitSHA)\")\n            let data = output\n                .trimmingCharacters(in: .newlines)\n                .components(separatedBy: \"\\n\")\n            return try data.compactMap { line -> GitChangedFile? in\n                let components = line.split(separator: \"\\t\")\n                guard components.count == 2 else { return nil }\n                let changeType = String(components[0])\n                let pathName = String(components[1])\n\n                guard let url = URL(string: pathName ) else {\n                    throw GitClientError.failedToDecodeURL\n                }\n\n                let gitType: GitStatus? = .init(rawValue: changeType)\n                let fullLink = self.directoryURL.appending(path: url.relativePath)\n\n                return GitChangedFile(\n                    status: gitType ?? .none,\n                    stagedStatus: .none,\n                    fileURL: fullLink,\n                    originalFilename: nil\n                )\n            }\n        } catch {\n            print(\"Error: \\(error)\")\n            return []\n        }\n    }\n\n    private func parseUnsyncedCommitsOutput(from string: String) throws -> (ahead: Int, behind: Int) {\n        let components = string.components(separatedBy: .newlines)\n        guard var abLine = components.first(where: { $0.starts(with: \"# branch.ab\") }) else {\n            // We're using --porcelain, this shouldn't happen\n            return (ahead: 0, behind: 0)\n        }\n        abLine = String(abLine.dropFirst(\"# branch.ab \".count))\n        let regex = Regex {\n            One(\"+\")\n            Capture {\n                OneOrMore(.digit)\n            } transform: { Int($0) }\n            One(\" -\")\n            Capture {\n                OneOrMore(.digit)\n            } transform: { Int($0) }\n        }\n        guard let match = try regex.firstMatch(in: abLine),\n              let ahead = match.output.1,\n              let behind = match.output.2 else {\n            return (ahead: 0, behind: 0)\n        }\n        return (ahead: ahead, behind: behind)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Client/GitClient+CommitHistory.swift",
    "content": "//\n//  GitClient+CommitHistory.swift\n//  CodeEdit\n//\n//  Created by Albert Vinizhanau on 10/20/23.\n//\n\nimport Foundation\n\nextension GitClient {\n    /// Gets the commit history log for the specified branch or file\n    /// - Parameters:\n    ///   - branchName: Name of the branch\n    ///   - maxCount: Maximum amount of entries to get\n    ///   - fileLocalPath: Optional path of file to get history for\n    /// - Returns: Array of git commits\n    func getCommitHistory(\n        branchName: String? = nil,\n        maxCount: Int? = nil,\n        fileLocalPath: String? = nil,\n        showMergeCommits: Bool = false\n    ) async throws -> [GitCommit] {\n        let branchString = branchName != nil ? \"\\\"\\(branchName ?? \"\")\\\"\" : \"\"\n        let fileString = fileLocalPath != nil ? \"\\\"\\(fileLocalPath ?? \"\")\\\"\" : \"\"\n        let countString = maxCount != nil ? \"-n \\(maxCount ?? 0)\" : \"\"\n\n        let dateFormatter = DateFormatter()\n\n        // Can't use `Locale.current`, since it'd give a nil date outside the US\n        dateFormatter.locale = Locale(identifier: Locale.current.identifier)\n        dateFormatter.dateFormat = \"EEE, dd MMM yyyy HH:mm:ss Z\"\n\n        let output = try await run(\n            \"\"\"\n            log \\(showMergeCommits ? \"\" : \"--no-merges\") -z \\\n            --pretty=%h¦%H¦%s¦%aN¦%ae¦%cn¦%ce¦%aD¦%b¦%D¦ \\\n            \\(countString) \\(branchString) -- \\(fileString)\n            \"\"\".trimmingCharacters(in: .whitespacesAndNewlines)\n        )\n        let remoteURL = try await getRemoteURL()\n\n        return output\n            .split(separator: \"\\0\")\n            .map { line -> GitCommit in\n                let parameters = String(line).components(separatedBy: \"¦\")\n                let infoRef = parameters[safe: 9]\n                var refs: [String] = []\n                var tag = \"\"\n                if let infoRef = infoRef {\n                    if infoRef.contains(\"tag:\") {\n                        tag = infoRef.components(separatedBy: \"tag:\")[1].trimmingCharacters(in: .whitespaces)\n                    } else {\n                        refs = infoRef.split(separator: \",\").compactMap {\n                            var element = String($0)\n                            if element.contains(\"origin/HEAD\") { return nil }\n                            if element.contains(\"HEAD -> \") {\n                                element = element.replacingOccurrences(of: \"HEAD -> \", with: \"\")\n                            }\n                            return element.trimmingCharacters(in: .whitespaces)\n                        }\n                    }\n                }\n\n                return GitCommit(\n                    hash: parameters[safe: 0] ?? \"\",\n                    commitHash: parameters[safe: 1] ?? \"\",\n                    message: parameters[safe: 2] ?? \"\",\n                    author: parameters[safe: 3] ?? \"\",\n                    authorEmail: parameters[safe: 4] ?? \"\",\n                    committer: parameters[safe: 5] ?? \"\",\n                    committerEmail: parameters[safe: 6] ?? \"\",\n                    body: parameters[safe: 8] ?? \"\",\n                    refs: refs,\n                    tag: tag,\n                    remoteURL: remoteURL,\n                    date: dateFormatter.date(from: parameters[safe: 7] ?? \"\") ?? Date()\n                )\n            }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Client/GitClient+Fetch.swift",
    "content": "//\n//  GitClient+Fetch.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/18/23.\n//\n\nimport Foundation\n\nextension GitClient {\n    /// Fetch changes to remote\n    func fetchFromRemote() async throws {\n        let command = \"fetch\"\n\n        _ = try await self.run(command)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Client/GitClient+Initiate.swift",
    "content": "//\n//  GitClient+Initiate.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/16/23.\n//\n\nimport Foundation\n\nextension GitClient {\n    /// Initiate Git repository\n    func initiate() async throws {\n        _ = try await run(\"init\")\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Client/GitClient+Pull.swift",
    "content": "//\n//  GitClient+Pull.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/18/23.\n//\n\nimport Foundation\n\nextension GitClient {\n    /// Pull changes from remote\n    func pullFromRemote(remote: String? = nil, branch: String? = nil, rebase: Bool = false) async throws {\n        var command = \"pull \\(rebase ? \"--rebase\" : \"--no-rebase\")\"\n\n        if let remote = remote, let branch = branch {\n            command += \" \\(remote) \\(branch)\"\n        }\n\n        _ = try await self.run(command)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Client/GitClient+Push.swift",
    "content": "//\n//  GitClient+Push.swift\n//  CodeEdit\n//\n//  Created by Albert Vinizhanau on 10/20/23.\n//\n\nimport Foundation\n\nextension GitClient {\n    /// Push changes to remote\n    func pushToRemote(\n        remote: String? = nil,\n        branch: String? = nil,\n        setUpstream: Bool? = false,\n        force: Bool? = false,\n        tags: Bool? = false\n    ) async throws {\n        var command = \"push\"\n        if let remote, let branch {\n            if setUpstream == true {\n                command += \" --set-upstream\"\n            }\n            if force == true {\n                command += \" --force\"\n            }\n            if tags == true {\n                command += \" --tags\"\n            }\n            command += \" \\(remote) \\(branch)\"\n        }\n\n        let output = try await self.run(command)\n\n        if output.contains(\"rejected\") {\n            throw GitClientError.outputError(output)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Client/GitClient+Remote.swift",
    "content": "//\n//  GitClient+Remote.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/17/23.\n//\n\nimport Foundation\n\nextension GitClient {\n    /// Gets all remotes\n    /// - Parameter name: Name for remote\n    /// - Parameter location: URL string for remote location\n    func getRemotes() async throws -> [GitRemote] {\n        let command = \"remote -v\"\n        let output = try await run(command)\n        let remotes = parseGitRemotes(from: output)\n\n        return remotes\n    }\n\n    /// Add existing remote to local git\n    /// - Parameter name: Name for remote\n    /// - Parameter location: URL string for remote location\n    func addRemote(name: String, location: String) async throws {\n        _ = try await run(\"remote add \\(name) \\(location)\")\n    }\n\n    /// Remove remote from local git\n    /// - Parameter name: Name for remote to remove\n    func removeRemote(name: String) async throws {\n        _ = try await run(\"remote rm \\(name)\")\n    }\n\n    /// Get the URL of the remote\n    /// > Note: If a git repository has multiple remotes, by default the `origin` remote\n    /// > will be used, unless there’s an upstream branch configured for the current branch.\n    /// > (Reference: https://git-scm.com/docs/git-ls-remote, https://git-scm.com/docs/git-fetch)\n    /// - Returns: A URL if a remote is configured, nil otherwise\n    /// - Throws: `GitClientError.outputError` if the underlying git command fails unexpectedly\n    func getRemoteURL() async throws -> URL? {\n        do {\n            let remote = try await run(\"ls-remote --get-url\")\n            return URL(string: remote.trimmingCharacters(in: .whitespacesAndNewlines))\n        } catch GitClientError.noRemoteConfigured {\n            return nil\n        } catch {\n            throw error\n        }\n    }\n}\n\nfunc parseGitRemotes(from output: String) -> [GitRemote] {\n    var remotes: [String: (fetch: String?, push: String?)] = [:]\n\n    output.split(separator: \"\\n\").forEach { line in\n        let components = line.split { $0 == \" \" || $0 == \"\\t\" }\n        guard components.count == 3 else { return }\n\n        let name = String(components[0])\n        let location = String(components[1])\n        let type = components[2].contains(\"(fetch)\") ? \"fetch\" : \"push\"\n\n        if var remote = remotes[name] {\n            if type == \"fetch\" {\n                remote.fetch = location\n            } else {\n                remote.push = location\n            }\n            remotes[name] = remote\n        } else {\n            if type == \"fetch\" {\n                remotes[name] = (fetch: location, push: nil)\n            } else {\n                remotes[name] = (fetch: nil, push: location)\n            }\n        }\n    }\n\n    return remotes.compactMap { name, locations in\n        if let fetchLocation = locations.fetch, let pushLocation = locations.push {\n            return GitRemote(name: name, pushLocation: pushLocation, fetchLocation: fetchLocation)\n        }\n        return nil\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Client/GitClient+Stash.swift",
    "content": "//\n//  GitClient+Stash.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/20/23.\n//\n\nimport Foundation\n\nextension GitClient {\n    /// Add uncommited changes to stash\n    func stash(message: String?) async throws {\n        let command = message != nil ? \"stash save --message=\\\"\\(message ?? \"\")\\\"\" : \"stash\"\n\n        _ = try await self.run(command)\n    }\n\n    /// Pops the latest entry from stash onto HEAD\n    func stashPop() async throws {\n        let command = \"stash pop\"\n\n        _ = try await self.run(command)\n    }\n\n    /// Lists all of the entries in stash\n    func stashList() async throws -> [GitStashEntry] {\n        let command = \"stash list --date=local\"\n        let output = try await run(command)\n        let stashEntries = parseGitStashEntries(output)\n\n        return stashEntries\n    }\n\n    /// Apply stash\n    func applyStashEntry(_ index: Int?) async throws {\n        if let idx = index {\n            _ = try await run(\"stash apply stash@{\\(idx)}\")\n        } else {\n            _ = try await run(\"stash apply\")\n        }\n    }\n\n    /// Delete stash\n    func deleteStashEntry(_ index: Int) async throws {\n        _ = try await run(\"stash drop stash@{\\(index)}\")\n    }\n}\n\nfunc parseGitStashEntries(_ input: String) -> [GitStashEntry] {\n    var entries: [GitStashEntry] = []\n\n    let trimmedInput = input.trimmingCharacters(in: .whitespacesAndNewlines)\n    let lines = trimmedInput.split(separator: \"\\n\", omittingEmptySubsequences: false)\n\n    let dateFormatter = DateFormatter()\n    dateFormatter.locale = Locale.current\n    dateFormatter.dateFormat = \"EEE MMM d HH:mm:ss yyyy\"\n\n    for (index, line) in lines.enumerated() {\n        let components = line.split(separator: \": \", maxSplits: 2, omittingEmptySubsequences: true)\n        guard components.count >= 3 else { continue }\n\n        let dateString = String(components[0].replacingOccurrences(of: \"stash@{\", with: \"\").dropLast())\n        guard let date = dateFormatter.date(from: dateString) else { continue }\n\n        // Re-join the remaining parts of the message (if there are colons in the message)\n        let message = components[2...].joined(separator: \": \").trimmingCharacters(in: .whitespaces)\n\n        let entry = GitStashEntry(index: index, message: message, date: date)\n        entries.append(entry)\n    }\n\n    return entries\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Client/GitClient+Status.swift",
    "content": "//\n//  GitClient+Status.swift\n//  CodeEdit\n//\n//  Created by Albert Vinizhanau on 10/20/23.\n//\n\nimport Foundation\n\n/// Methods for parsing git's porcelain v2 format and returning the info in a ``GitClient/Status`` struct.\n///\n/// Git defines five types of changes to parse in the v2 format:\n/// - Ordinary\n/// - Renamed/Copied\n/// - Unmerged\n/// - Untracked\n/// - Ignored\n///\n/// These are documented here: https://git-scm.com/docs/git-status.\n///\n/// There is one method for each change type that can be returned with the exception of ignored which is, well, ignored.\n///\n/// # TODO:\n/// In the future, this method should return information about push/pull status and stash status, as that\n/// information can be included in the same call.\n\nextension GitClient {\n    struct Status {\n        var changedFiles: [GitChangedFile]\n        var unmergedChanges: [GitChangedFile]\n        var untrackedFiles: [GitChangedFile]\n    }\n\n    /// Fetches and parses the git repository's status.\n    /// - Returns: A ``GitClient/Status`` struct with information about the changed files in the repository.\n    /// - Throws: Can throw ``GitClient/GitClientError`` errors if it finds unexpected output.\n    func getStatus() async throws -> Status {\n        let output = try await run(\"status -z --porcelain=2 -u\")\n        return try parseStatusString(output)\n    }\n\n    /// Parses a status string from ``getStatus()`` and returns a ``Status`` object if possible.\n    /// - Parameter output: The git output from running `status`. Expects a porcelain v2 string.\n    /// - Returns: A status object if parseable.\n    func parseStatusString(_ output: borrowing String) throws -> Status {\n        let endsInNull = output.last == Character(UnicodeScalar(0))\n        let endIndex: String.Index\n        if endsInNull && output.count > 1 {\n            endIndex = output.index(before: output.endIndex)\n        } else {\n            endIndex = output.endIndex\n        }\n\n        var status = Status(changedFiles: [], unmergedChanges: [], untrackedFiles: [])\n\n        var index = output.startIndex\n        while index < endIndex {\n            let typeIndex = index\n\n            // Move ahead no matter what.\n            guard let nextIndex = output.safeOffset(index, offsetBy: 2) else {\n                throw GitClientError.statusParseEarlyEnd\n            }\n            index = nextIndex\n\n            switch output[typeIndex] {\n            case \"1\": // Ordinary changes\n                status.changedFiles.append(try parseOrdinary(index: &index, output: output))\n            case \"2\": // Renamed or copied changes\n                status.changedFiles.append(try parseRenamed(index: &index, output: output))\n            case \"u\": // Unmerged changes\n                status.unmergedChanges.append(try parseUnmerged(index: &index, output: output))\n            case \"?\": // Untracked files\n                status.untrackedFiles.append(try parseUntracked(index: &index, output: output))\n            case \"!\", \"#\": // Ignored files or Header\n                try substringToNextNull(from: &index, output: output) // move the index to the next line.\n            default:\n                throw GitClientError.statusInvalidChangeType(output[typeIndex])\n            }\n        }\n\n        return status\n    }\n\n    /// Discard changes for file\n    func discardChanges(for file: URL) async throws {\n        _ = try await run(\"restore '\\(file.path(percentEncoded: false))'\")\n    }\n\n    /// Discard unstaged changes\n    func discardAllChanges() async throws {\n        _ = try await run(\"restore .\")\n    }\n\n    // MARK: - Parsing Helpers\n\n    // Note for the following methods we make extensive use of the `borrowing` parameter modifier to avoid\n    // ever copying the output. If changes are made to these methods, ensure this invariant is maintained for\n    // performance.\n\n    /// Finds the substring up until the next null character. Does not include the null char.\n    /// - Parameters:\n    ///   - index: The current index. Modified to be after the null char.\n    ///   - output: The string from the git command, borrowed.\n    /// - Returns: A substring with the contents of the string up until a null char.\n    /// - Throws: Throws a `GitClientError` if the end of the string is found early.\n    @discardableResult\n    fileprivate func substringToNextNull(from index: inout String.Index, output: borrowing String) throws -> Substring {\n        let startIndex = index\n        while output[index] != Character(UnicodeScalar(0)) {\n            let newIndex = output.index(after: index)\n            guard newIndex < output.endIndex else {\n                throw GitClientError.statusParseEarlyEnd\n            }\n            index = newIndex\n        }\n        defer {\n            if index < output.index(before: output.endIndex) {\n                index = output.index(after: index)\n            }\n        }\n        return output[startIndex..<index]\n    }\n\n    /// Move the index to the next space char.\n    /// - Throws: Throws a `GitClientError` if the end of the string is found early.\n    fileprivate func moveToNextSpace(from index: inout String.Index, output: borrowing String) throws {\n        repeat {\n            try moveOneChar(from: &index, output: output)\n        }\n        while output[index] != \" \"\n    }\n\n    /// Move the index one character.\n    /// - Throws: Throws a `GitClientError` if the end of the string is found early.\n    fileprivate func moveOneChar(from index: inout String.Index, output: borrowing String) throws {\n        index = output.index(after: index)\n        guard index != output.endIndex else {\n            throw GitClientError.statusParseEarlyEnd\n        }\n    }\n\n    /// Parse a status character at the current index.\n    /// - Returns: The status, if any.\n    /// - Throws: Throws a `GitClientError` if an invalid status character is found.\n    fileprivate func parseStatus(index: inout String.Index, output: borrowing String) throws -> GitStatus {\n        guard let status = GitStatus(rawValue: String(output[index])) else {\n            throw GitClientError.invalidStatus(output[index])\n        }\n        index = output.index(after: index)\n        return status\n    }\n\n    // MARK: - Change Type Parsers\n\n    /// Parses an ordinary change.\n    /// ```\n    /// 1 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <path>\n    /// ```\n    fileprivate func parseOrdinary(index: inout String.Index, output: borrowing String) throws -> GitChangedFile {\n        let stagedStatus = try parseStatus(index: &index, output: output)\n        let status = try parseStatus(index: &index, output: output)\n        // don't care about fields\n        for _ in 0..<6 {\n            try moveToNextSpace(from: &index, output: output)\n        }\n        try moveOneChar(from: &index, output: output)\n        let substring = try substringToNextNull(from: &index, output: output)\n        let filename = String(substring)\n        return GitChangedFile(\n            status: status,\n            stagedStatus: stagedStatus,\n            fileURL: URL(filePath: filename, relativeTo: directoryURL),\n            originalFilename: nil\n        )\n    }\n\n    /// Parses a renamed or copied change.\n    /// ```\n    /// 2 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <X><score> <path><sep><origPath>\n    /// ```\n    fileprivate func parseRenamed(index: inout String.Index, output: borrowing String) throws -> GitChangedFile {\n        let stagedStatus = try parseStatus(index: &index, output: output)\n        let status = try parseStatus(index: &index, output: output)\n        // don't care about fields\n        for _ in 0..<7 {\n            try moveToNextSpace(from: &index, output: output)\n        }\n        try moveOneChar(from: &index, output: output)\n        let filename = String(try substringToNextNull(from: &index, output: output))\n        let originalFilename = String(try substringToNextNull(from: &index, output: output))\n        return GitChangedFile(\n            status: status,\n            stagedStatus: stagedStatus,\n            fileURL: URL(filePath: filename, relativeTo: directoryURL),\n            originalFilename: originalFilename\n        )\n    }\n\n    /// Parses an unmerged change.\n    /// ```\n    /// u <XY> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path>\n    /// ```\n    fileprivate func parseUnmerged(index: inout String.Index, output: borrowing String) throws -> GitChangedFile {\n        let stagedStatus = try parseStatus(index: &index, output: output)\n        let status = try parseStatus(index: &index, output: output)\n        // don't care about fields\n        for _ in 0..<8 {\n            try moveToNextSpace(from: &index, output: output)\n        }\n        try moveOneChar(from: &index, output: output)\n        let filename = String(try substringToNextNull(from: &index, output: output))\n        return GitChangedFile(\n            status: status,\n            stagedStatus: stagedStatus,\n            fileURL: URL(filePath: filename, relativeTo: directoryURL),\n            originalFilename: nil\n        )\n    }\n\n    /// Parses an untracked change.\n    /// ```\n    /// ? <path>\n    /// ```\n    fileprivate func parseUntracked(index: inout String.Index, output: borrowing String) throws -> GitChangedFile {\n        let filename = String(try substringToNextNull(from: &index, output: output))\n        return GitChangedFile(\n            status: .untracked,\n            stagedStatus: .none,\n            fileURL: URL(filePath: filename, relativeTo: directoryURL),\n            originalFilename: nil\n        )\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Client/GitClient+Validate.swift",
    "content": "//\n//  GitClient+Validate.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/29/23.\n//\n\nimport Foundation\n\nextension GitClient {\n    /// Determines if the current directory is a valid git repository.\n    ///\n    /// Runs `git rev-parse --is-inside-work-tree`.\n    ///\n    /// - Returns: True, if git finds a valid repository.\n    func validate() async -> Bool {\n        do {\n            let output = try await run(\"rev-parse --is-inside-work-tree\")\n            return output.trimmingCharacters(in: .whitespacesAndNewlines) == \"true\"\n        } catch {\n            return false\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Client/GitClient.swift",
    "content": "//\n//  GitClient.swift\n//  CodeEdit\n//\n//  Created by Matthijs Eikelenboom on 26/11/2022.\n//\n\nimport Combine\nimport Foundation\nimport OSLog\n\nclass GitClient {\n    enum GitClientError: Error {\n        case outputError(String)\n        case notGitRepository\n        case failedToDecodeURL\n        case noRemoteConfigured\n        // Status parsing\n        case statusParseEarlyEnd\n        case invalidStatus(_ char: Character)\n        case statusInvalidChangeType(_ type: Character)\n\n        var description: String {\n            switch self {\n            case .outputError(let string): string\n            case .notGitRepository: \"Not a git repository\"\n            case .failedToDecodeURL: \"Failed to decode URL\"\n            case .noRemoteConfigured: \"No remote configured\"\n            case .statusParseEarlyEnd: \"Invalid status, found end of string too early\"\n            case let .invalidStatus(char): \"Invalid status received: \\(char)\"\n            case let .statusInvalidChangeType(char): \"Status invalid change type: \\(char)\"\n            }\n        }\n    }\n\n    let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? \"\", category: \"GitClient\")\n\n    internal let directoryURL: URL\n    internal let shellClient: ShellClient\n\n    private let configClient: GitConfigClient\n\n    init(directoryURL: URL, shellClient: ShellClient) {\n        self.directoryURL = directoryURL\n        self.shellClient = shellClient\n        self.configClient = GitConfigClient(projectURL: directoryURL, shellClient: shellClient)\n    }\n\n    func getConfig<T: GitConfigRepresentable>(key: String) async throws -> T? {\n        return try await configClient.get(key: key, global: false)\n    }\n\n    func setConfig<T: GitConfigRepresentable>(key: String, value: T) async {\n        await configClient.set(key: key, value: value, global: false)\n    }\n\n    /// Runs a git command, it will prepend the command with `cd <directoryURL>;git`,\n    /// If you need to run \"git checkout\", pass \"checkout\" as the command parameter\n    internal func run(_ command: String) async throws -> String {\n        let output = try shellClient.run(generateCommand(command))\n        return try processCommonErrors(output)\n    }\n\n    internal typealias LiveCommandStream = AsyncThrowingMapSequence<AsyncThrowingStream<String, Error>, String>\n\n    /// Runs a git command in same way as `run`, but returns a async stream of the output\n    internal func runLive(_ command: String) -> LiveCommandStream {\n        return runLive(customCommand: generateCommand(command))\n    }\n\n    /// Here you can run a custom command, this is needed for git clone\n    internal func runLive(customCommand: String) -> LiveCommandStream {\n        return shellClient\n            .runAsync(customCommand)\n            .map { output in\n                return try self.processCommonErrors(output)\n            }\n    }\n\n    private func generateCommand(_ command: String) -> String {\n        \"cd \\(directoryURL.relativePath.escapedDirectory());git \\(command)\"\n    }\n\n    private func processCommonErrors(_ output: String) throws -> String {\n        if output.contains(\"fatal: not a git repository\") {\n            throw GitClientError.notGitRepository\n        }\n\n        if output.contains(\"fatal: No remote configured\") {\n            throw GitClientError.noRemoteConfigured\n        }\n\n        if output.hasPrefix(\"fatal:\") {\n            throw GitClientError.outputError(output)\n        }\n\n        return output\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Client/GitConfigClient.swift",
    "content": "//\n//  GitConfigClient.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 10/31/24.\n//\n\nimport Foundation\n\n/// A client for managing Git configuration settings.\n/// Provides methods to read and write Git configuration values at both\n/// project and global levels.\nclass GitConfigClient {\n    private let projectURL: URL?\n    private let shellClient: ShellClient\n\n    /// Initializes a new GitConfigClient.\n    /// - Parameters:\n    ///   - projectURL: The project directory URL (if any).\n    ///   - shellClient: The client responsible for executing shell commands.\n    init(projectURL: URL? = nil, shellClient: ShellClient) {\n        self.projectURL = projectURL\n        self.shellClient = shellClient\n    }\n\n    /// Runs a Git configuration command.\n    /// - Parameters:\n    ///   - command: The Git command to execute.\n    ///   - global: Whether to apply the command globally or locally.\n    /// - Returns: The command output as a string.\n    private func runConfigCommand(_ command: String, global: Bool) async throws -> String {\n        var fullCommand = \"git config\"\n\n        if global {\n            fullCommand += \" --global\"\n        } else if let projectURL = projectURL {\n            fullCommand = \"cd \\(projectURL.relativePath.escapedDirectory()); \" + fullCommand\n        }\n\n        fullCommand += \" \\(command)\"\n        return try shellClient.run(fullCommand)\n    }\n\n    /// Retrieves a Git configuration value.\n    /// - Parameters:\n    ///   - key: The configuration key to retrieve.\n    ///   - global: Whether to retrieve the value globally or locally.\n    /// - Returns: The value as a type conforming to `GitConfigRepresentable`, or `nil` if not found.\n    func get<T: GitConfigRepresentable>(key: String, global: Bool = false) async throws -> T? {\n        let output = try await runConfigCommand(key, global: global)\n        let trimmedOutput = output.trimmingCharacters(in: .whitespacesAndNewlines)\n        return T(configValue: trimmedOutput)\n    }\n\n    /// Sets a Git configuration value.\n    /// - Parameters:\n    ///   - key: The configuration key to set.\n    ///   - value: The value to set, conforming to `GitConfigRepresentable`.\n    ///   - global: Whether to set the value globally or locally.\n    func set<T: GitConfigRepresentable>(key: String, value: T, global: Bool = false) async {\n        let shouldUnset: Bool\n        if let boolValue = value as? Bool {\n            shouldUnset = !boolValue\n        } else if let stringValue = value as? String {\n            shouldUnset = stringValue.isEmpty\n        } else {\n            shouldUnset = false\n        }\n\n        let commandString = shouldUnset ? \"--unset \\(key)\" : \"\\(key) \\(value.asConfigValue)\"\n\n        do {\n            _ = try await runConfigCommand(commandString, global: global)\n        } catch {\n            print(\"Failed to set \\(key): \\(error)\")\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Client/GitConfigExtensions.swift",
    "content": "//\n//  GitConfigExtensions.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/16/24.\n//\n\nimport Foundation\n\n/// Conformance of `Bool` to `GitConfigRepresentable`\n///\n/// This enables `Bool` values to be represented in Git configuration as\n/// `true` or `false`.\nextension Bool: GitConfigRepresentable {\n    public init?(configValue: String) {\n        switch configValue.lowercased() {\n        case \"true\": self = true\n        case \"false\": self = false\n        default: return nil\n        }\n    }\n\n    public var asConfigValue: String {\n        self ? \"true\" : \"false\"\n    }\n}\n\n/// Conformance of `String` to `GitConfigRepresentable`\n///\n/// This enables `String` values to be represented in Git configuration,\n/// automatically escaping them with quotes.\nextension String: GitConfigRepresentable {\n    public init?(configValue: String) {\n        self = configValue\n    }\n\n    public var asConfigValue: String {\n        \"\\\"\\(self)\\\"\"\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Client/GitConfigRepresentable.swift",
    "content": "//\n//  GitConfigRepresentable.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/16/24.\n//\n\n/// A protocol that provides a mechanism to represent and parse Git configuration values.\n///\n/// Conforming types must be able to initialize from a Git configuration string\n/// and convert their value back to a Git-compatible string representation.\nprotocol GitConfigRepresentable {\n    /// Initializes a new instance from a Git configuration value string.\n    /// - Parameter configValue: The configuration value string.\n    init?(configValue: String)\n\n    /// Converts the value to a Git-compatible configuration string.\n    var asConfigValue: String { get }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Clone/GitCheckoutBranchView.swift",
    "content": "//\n//  GitCheckoutBranchView.swift\n//  CodeEditModules/Git\n//\n//  Created by Aleksi Puttonen on 14.4.2022.\n//\n\nimport Foundation\nimport SwiftUI\n\nstruct GitCheckoutBranchView: View {\n    @Environment(\\.dismiss)\n    private var dismiss\n\n    @StateObject private var viewModel: GitCheckoutBranchViewModel\n    private var openDocument: (URL) -> Void\n\n    init(\n        repoLocalPath: URL,\n        openDocument: @escaping (URL) -> Void\n    ) {\n        _viewModel = .init(wrappedValue: GitCheckoutBranchViewModel(repoPath: repoLocalPath))\n        self.openDocument = openDocument\n    }\n    var body: some View {\n        VStack(spacing: 8) {\n            HStack {\n                Image(nsImage: NSApp.applicationIconImage)\n                    .resizable()\n                    .frame(width: 64, height: 64)\n                    .padding(.bottom, 50)\n                VStack(alignment: .leading) {\n                    Text(\"Checkout branch\")\n                        .bold()\n                        .padding(.bottom, 2)\n                    Text(\"Select a branch to checkout\")\n                        .font(.system(size: 11))\n                        .foregroundColor(.secondary)\n                        .alignmentGuide(.trailing) { context in\n                        context[.trailing]\n                    }\n                    Picker(\"\", selection: $viewModel.selectedBranch, content: {\n                        ForEach(viewModel.branches, id: \\.self) { branch in\n                            Text(branch.name.replacingOccurrences(of: \"origin/\", with: \"\"))\n                                .tag(branch as GitBranch?)\n                        }\n                    })\n                    .labelsHidden()\n\n                    HStack {\n                        Button(\"Cancel\") {\n                            dismiss()\n                        }\n                        Button(\"Checkout\") {\n                            Task {\n                                await viewModel.checkoutBranch()\n                                await MainActor.run {\n                                    dismiss()\n                                    openDocument(viewModel.repoPath)\n                                }\n                            }\n                        }\n                        .keyboardShortcut(.defaultAction)\n                    }\n                    .alignmentGuide(.trailing) { context in\n                        context[.trailing]\n                    }\n                    .offset(x: 145)\n                }\n            }\n            .padding(.top, 20)\n            .padding(.horizontal, 20)\n            .padding(.bottom, 16)\n            .frame(width: 400)\n            .task {\n                await viewModel.loadBranches()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Clone/GitCloneView.swift",
    "content": "//\n//  GitCloneView.swift\n//  CodeEditModules/Git\n//\n//  Created by Aleksi Puttonen on 23.3.2022.\n//\n\nimport SwiftUI\nimport Foundation\nimport Combine\n\nstruct GitCloneView: View {\n    @Environment(\\.dismiss)\n    private var dismiss\n\n    @StateObject private var viewModel: GitCloneViewModel = .init()\n\n    private let openBranchView: (URL) -> Void\n    private let openDocument: (URL) -> Void\n\n    init(\n        openBranchView: @escaping (URL) -> Void,\n        openDocument: @escaping (URL) -> Void\n    ) {\n        self.openBranchView = openBranchView\n        self.openDocument = openDocument\n    }\n\n    var body: some View {\n        VStack(spacing: 8) {\n            HStack(alignment: .top) {\n                Image(nsImage: NSApp.applicationIconImage)\n                    .resizable()\n                    .frame(width: 64, height: 64)\n                VStack(alignment: .leading) {\n                    Text(\"Clone a Repository\")\n                        .bold()\n                        .padding(.bottom, 2)\n                    Text(\"Enter a git repository URL:\")\n                        .font(.system(size: 11))\n                        .foregroundColor(.secondary)\n                        .alignmentGuide(.trailing) { context in\n                            context[.trailing]\n                        }\n                    TextField(\"Git Repository URL\", text: $viewModel.repoUrlStr)\n                        .lineLimit(1)\n                        .padding(.bottom, 15)\n\n                    HStack {\n                        Spacer()\n                        Button(\"Cancel\") {\n                            dismiss()\n                        }\n                        Button(\"Clone\") {\n                            cloneRepository()\n                        }\n                        .keyboardShortcut(.defaultAction)\n                        .disabled(!viewModel.isValidUrl(url: viewModel.repoUrlStr))\n                    }\n                }\n                .frame(width: 300)\n            }\n            .padding(.top, 20)\n            .padding(.horizontal, 20)\n            .padding(.bottom, 16)\n            .onAppear {\n                viewModel.checkClipboard()\n            }\n            .sheet(isPresented: $viewModel.isCloning) {\n                cloningSheet\n            }\n        }\n    }\n\n    @ViewBuilder private var cloningSheet: some View {\n        NavigationStack {\n            VStack {\n                ProgressView(\n                    viewModel.cloningProgress.state.label,\n                    value: viewModel.cloningProgress.progress,\n                    total: 100\n                )\n            }\n        }\n        .toolbar {\n            ToolbarItem {\n                Button(\"Cancel Cloning\") {\n                    viewModel.cloningTask?.cancel()\n                    viewModel.cloningTask = nil\n                    viewModel.isCloning = false\n                }\n            }\n        }\n        .padding()\n        .frame(width: 350)\n    }\n\n    func cloneRepository() {\n        viewModel.cloneRepository { localPath in\n            dismiss()\n\n            guard let gitClient = viewModel.gitClient else { return }\n\n            Task {\n                let branches = ((try? await  gitClient.getBranches()) ?? [])\n                    .filter({ $0.isRemote })\n                if branches.count > 1 {\n                    openBranchView(localPath)\n                    return\n                }\n\n                openDocument(localPath)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Clone/ViewModels/GitCheckoutBranchViewModel.swift",
    "content": "//\n//  GitCheckoutBranchView.swift\n//  CodeEdit\n//\n//  Created by Albert Vinizhanau on 10/17/23.\n//\n\nimport Foundation\n\nclass GitCheckoutBranchViewModel: ObservableObject {\n    @Published var selectedBranch: GitBranch?\n    @Published var branches: [GitBranch] = []\n\n    let repoPath: URL\n    private let gitClient: GitClient\n\n    init(repoPath: URL) {\n        self.repoPath = repoPath\n        gitClient = .init(directoryURL: repoPath, shellClient: .live())\n    }\n\n    func loadBranches() async {\n        let branches = ((try? await gitClient.getBranches()) ?? [])\n            .filter({ $0.isRemote })\n\n        await MainActor.run {\n            self.branches = branches\n            if selectedBranch == nil {\n                selectedBranch = branches.first\n            }\n        }\n    }\n\n    func checkoutBranch() async {\n        guard let selectedBranch else { return }\n\n        try? await gitClient.checkoutBranch(selectedBranch)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Clone/ViewModels/GitCloneViewModel.swift",
    "content": "//\n//  GitCloneViewModel.swift\n//  CodeEdit\n//\n//  Created by Albert Vinizhanau on 10/17/23.\n//\n\nimport Foundation\nimport AppKit\n\nclass GitCloneViewModel: ObservableObject {\n    @Published var repoUrlStr = \"\"\n    @Published var isCloning: Bool = false\n    @Published var cloningProgress: GitClient.CloneProgress = .init(progress: 0, state: .initialState)\n\n    var gitClient: GitClient?\n    var cloningTask: Task<Void, Error>?\n\n    /// Check if url is valid\n    /// - Parameter url: Url to check\n    /// - Returns: True if url is valid\n    func isValidUrl(url: String) -> Bool {\n        // Doing the same kind of check that Xcode does when cloning\n        let url = url.lowercased()\n        if url.starts(with: \"http://\") && url.count > 7 {\n            return true\n        } else if url.starts(with: \"https://\") && url.count > 8 {\n            return true\n        } else if url.starts(with: \"git@\") && url.count > 4 {\n            return true\n        }\n        return false\n    }\n    /// Check if Git is installed\n    /// - Returns: True if Git is found by running \"which git\" command\n    func isGitInstalled() -> Bool {\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: \"/usr/bin/which\")\n        process.arguments = [\"git\"]\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        do {\n            try process.run()\n            process.waitUntilExit()\n            return process.terminationStatus == 0\n        } catch {\n            return false\n        }\n    }\n\n    /// Check if clipboard contains git url\n    func checkClipboard() {\n        if let url = NSPasteboard.general.pasteboardItems?.first?.string(forType: .string) {\n            if isValidUrl(url: url) {\n                self.repoUrlStr = url\n            }\n        }\n    }\n\n    /// Clone repository\n    func cloneRepository(completionHandler: @escaping (URL) -> Void) {\n        if !isGitInstalled() {\n            showAlert(\n                alertMsg: \"Git installation not found.\",\n                infoText: \"Ensure Git is installed on your system and try again.\"\n            )\n            return\n        }\n        if repoUrlStr == \"\" {\n            showAlert(\n                alertMsg: \"Url cannot be empty\",\n                infoText: \"You must specify a repository to clone\"\n            )\n            return\n        }\n\n        // Parsing repo name\n        guard let remoteUrl = URL(string: repoUrlStr) else {\n            return\n        }\n\n        var repoName = remoteUrl.lastPathComponent\n\n        // Strip .git from name if it has it.\n        // Cloning repository without .git also works\n        if repoName.contains(\".git\") {\n            repoName.removeLast(4)\n        }\n\n        guard let localPath = getPath(saveName: repoName) else {\n            return\n        }\n\n        var isDir: ObjCBool = true\n        if FileManager.default.fileExists(atPath: localPath.relativePath, isDirectory: &isDir) {\n            showAlert(alertMsg: \"Error\", infoText: \"Directory already exists\")\n            return\n        }\n\n        do {\n            try FileManager.default.createDirectory(\n                atPath: localPath.relativePath,\n                withIntermediateDirectories: true,\n                attributes: nil\n            )\n        } catch {\n            showAlert(alertMsg: \"Failed to create folder\", infoText: \"\\(error)\")\n            return\n        }\n\n        gitClient = GitClient(directoryURL: localPath, shellClient: .live())\n\n        self.cloningTask = Task(priority: .background) {\n            await processCloning(\n                remoteUrl: remoteUrl,\n                localPath: localPath,\n                completionHandler: completionHandler\n            )\n        }\n    }\n\n    /// Process cloning\n    /// - Parameters:\n    ///   - remoteUrl: Path to remote repository\n    ///   - localPath: Path to local folder\n    ///   - completionHandler: Completion handler if cloning is successful\n    private func processCloning(\n        remoteUrl: URL,\n        localPath: URL,\n        completionHandler: @escaping (URL) -> Void\n    ) async {\n        guard let gitClient else { return }\n\n        await setIsCloning(true)\n\n        do {\n            for try await progress in gitClient.cloneRepository(remoteUrl: remoteUrl, localPath: localPath) {\n                await MainActor.run {\n                    self.cloningProgress = progress\n                }\n            }\n\n            if Task.isCancelled {\n                await MainActor.run {\n                    deleteTemporaryFolder(localPath: localPath)\n                }\n                return\n            }\n\n            completionHandler(localPath)\n        } catch {\n            await MainActor.run {\n                if let error = error as? GitClient.GitClientError {\n                    showAlert(alertMsg: \"Failed to clone\", infoText: error.description)\n                } else {\n                    showAlert(alertMsg: \"Failed to clone\", infoText: error.localizedDescription)\n                }\n                deleteTemporaryFolder(localPath: localPath)\n            }\n        }\n\n        await setIsCloning(false)\n    }\n\n    private func deleteTemporaryFolder(localPath: URL) {\n        do {\n            try FileManager.default.removeItem(atPath: localPath.relativePath)\n        } catch {\n            showAlert(alertMsg: \"Failed to delete folder\", infoText: \"\\(error)\")\n            return\n        }\n    }\n\n    @MainActor\n    private func setIsCloning(_ newValue: Bool) {\n        self.isCloning = newValue\n    }\n\n    private func getPath(saveName: String) -> URL? {\n        let dialog = NSSavePanel()\n        dialog.showsResizeIndicator = true\n        dialog.showsHiddenFiles = false\n        dialog.showsTagField = false\n        dialog.prompt = \"Clone\"\n        dialog.nameFieldStringValue = saveName\n        dialog.nameFieldLabel = \"Clone as\"\n        dialog.title = \"Clone a Repository\"\n\n        guard dialog.runModal() == NSApplication.ModalResponse.OK,\n              let result = dialog.url else {\n            return nil\n        }\n\n        return result\n    }\n\n    private func showAlert(alertMsg: String, infoText: String) {\n        let alert = NSAlert()\n        alert.messageText = alertMsg\n        alert.informativeText = infoText\n        alert.addButton(withTitle: \"OK\")\n        alert.alertStyle = .warning\n        alert.runModal()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Models/GitBranch.swift",
    "content": "//\n//  GitBranch.swift\n//  CodeEdit\n//\n//  Created by Albert Vinizhanau on 10/20/23.\n//\n\nimport Foundation\n\nstruct GitBranch: Hashable, Identifiable {\n    let name: String\n    let longName: String\n    let upstream: String?\n    let ahead: Int\n    let behind: Int\n\n    var id: String {\n        longName\n    }\n\n    /// Is local branch\n    var isLocal: Bool {\n        return longName.hasPrefix(\"refs/heads/\")\n    }\n\n    /// Is remote branch\n    var isRemote: Bool {\n        return longName.hasPrefix(\"refs/remotes/\")\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Models/GitBranchesGroup.swift",
    "content": "//\n//  GitBranchesGroup.swift\n//  CodeEdit\n//\n//  Created by Federico Zivolo on 22/01/24.\n//\n\nimport Foundation\n\nstruct GitBranchesGroup: Hashable {\n    let name: String\n    var branches: [GitBranch]\n    var shouldNest: Bool {\n        branches.first?.name.hasPrefix(name + \"/\") ?? false\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Models/GitChangedFile.swift",
    "content": "//\n//  ChangedFile.swift\n//  \n//\n//  Created by Nanashi Li on 2022/05/20.\n//\n\nimport Foundation\nimport SwiftUI\n\n/// Represents a single changed file in the working tree.\nstruct GitChangedFile: Identifiable, Hashable {\n    var id: String { fileURL.relativePath }\n\n    /// The status of the file.\n    let status: GitStatus\n    /// The staged status of the file. A non-`none` value here and in ``status`` may indicate a file that was added\n    /// but has since been changed and needs to be re-added before committing.\n    let stagedStatus: GitStatus\n\n    /// URL of the file\n    let fileURL: URL\n\n    /// The original file name if ``status`` or ``stagedStatus`` is `renamed` or `copied`\n    let originalFilename: String?\n\n    /// Returns the user-facing status, if ``status`` is `none`, returns ``stagedStatus``.\n    func anyStatus() -> GitStatus {\n        if case .none = status {\n            return stagedStatus\n        }\n        return status\n    }\n\n    var isStaged: Bool {\n        stagedStatus != .none\n    }\n\n    /// Use this string to find matching `CEWorkspaceFile`s in the workspace file manager.\n    var ceFileKey: String {\n        fileURL.absoluteURL.path(percentEncoded: false)\n    }\n\n    func hash(into hasher: inout Hasher) {\n        hasher.combine(fileURL)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Models/GitCommit.swift",
    "content": "//\n//  GitCommit.swift\n//  CodeEditModules/Git\n//\n//  Created by Marco Carnevali on 27/03/22.\n//\n\nimport Foundation.NSDate\n\n/// Model class to help map commit history log data\nstruct GitCommit: Equatable, Hashable, Identifiable {\n    var id = UUID()\n    let hash: String\n    let commitHash: String\n    let message: String\n    let author: String\n    let authorEmail: String\n    let committer: String\n    let committerEmail: String\n    let body: String\n    let refs: [String]\n    let tag: String\n    let remoteURL: URL?\n    let date: Date\n\n    var commitBaseURL: URL? {\n        if let remoteURL {\n            if remoteURL.absoluteString.contains(\"github\") {\n                return parsedRemoteUrl(domain: \"https://github.com\", remote: remoteURL)\n            }\n            if remoteURL.absoluteString.contains(\"bitbucket\") {\n                return parsedRemoteUrl(domain: \"https://bitbucket.org\", remote: remoteURL)\n            }\n            if remoteURL.absoluteString.contains(\"gitlab\") {\n                return parsedRemoteUrl(domain: \"https://gitlab.com\", remote: remoteURL)\n            }\n            // TODO: Implement other git clients other than github, bitbucket here\n        }\n        return nil\n    }\n\n    private func parsedRemoteUrl(domain: String, remote: URL) -> URL {\n        // There are 2 types of remotes - https and ssh. While https has URL in its name, ssh doesn't.\n        // Following code takes remote name in format profileName/repoName and prepends according domain\n        var formattedRemote = remote\n        if formattedRemote.absoluteString.starts(with: \"git@\") {\n            let parts = formattedRemote.absoluteString.components(separatedBy: \":\")\n            formattedRemote = URL.init(fileURLWithPath: \"\\(domain)/\\(parts[parts.count - 1])\")\n        }\n\n        return formattedRemote.deletingPathExtension().appending(path: \"commit\")\n    }\n\n    var remoteString: String {\n        if let remoteURL {\n            if remoteURL.absoluteString.contains(\"github\") {\n                return \"GitHub\"\n            }\n            if remoteURL.absoluteString.contains(\"bitbucket\") {\n                return \"BitBucket\"\n            }\n            if remoteURL.absoluteString.contains(\"gitlab\") {\n                return \"GitLab\"\n            }\n        }\n        return \"Remote\"\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Models/GitRemote.swift",
    "content": "//\n//  GitRemote.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/17/23.\n//\n\nimport Foundation\n\nstruct GitRemote: Hashable {\n    let name: String\n    let pushLocation: String\n    let fetchLocation: String\n    var branches: [GitBranch] = []\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Models/GitStashEntry.swift",
    "content": "//\n//  GitStashEntry.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/20/23.\n//\n\nimport Foundation\n\nstruct GitStashEntry: Hashable {\n    let index: Int\n    let message: String\n    let date: Date\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Models/GitStatus.swift",
    "content": "//\n//  GitType.swift\n//  \n//\n//  Created by Nanashi Li on 2022/05/20.\n//\n\nimport Foundation\n\nenum GitStatus: String, Codable {\n    case none = \".\"\n    case modified = \"M\"\n    case untracked = \"?\"\n    case fileTypeChange = \"T\"\n    case added = \"A\"\n    case deleted = \"D\"\n    case renamed = \"R\"\n    case copied = \"C\"\n    case unmerged = \"U\"\n\n    var description: String {\n        switch self {\n        case .modified: return \"M\"\n        case .untracked: return \"U\"\n        case .fileTypeChange: return \"T\"\n        case .added: return \"A\"\n        case .deleted: return \"D\"\n        case .renamed: return \"R\"\n        case .copied: return \"C\"\n        case .unmerged: return \"U\"\n        case .none: return \"\"\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/SourceControlManager+GitClient.swift",
    "content": "//\n//  SourceControlManager+GitClient.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 7/2/24.\n//\n\nimport Foundation\n\nextension SourceControlManager {\n    /// Validate repository\n    func validate() async throws {\n        let isGitRepository = await gitClient.validate()\n        await MainActor.run {\n            self.isGitRepository = isGitRepository\n        }\n    }\n\n    /// Fetch from remote\n    func fetch() async throws {\n        try await gitClient.fetchFromRemote()\n        await self.refreshNumberOfUnsyncedCommits()\n    }\n\n    /// Refresh current branch\n    func refreshCurrentBranch() async {\n        let currentBranch = try? await gitClient.getCurrentBranch()\n        await MainActor.run {\n            self.currentBranch = currentBranch\n        }\n    }\n\n    /// Refresh branches\n    func refreshBranches() async {\n        let branches = (try? await gitClient.getBranches()) ?? []\n        await MainActor.run {\n            self.branches = branches\n        }\n    }\n\n    /// Checkout branch\n    func checkoutBranch(branch: GitBranch) async throws {\n        try await gitClient.checkoutBranch(branch)\n        await refreshBranches()\n        await refreshCurrentBranch()\n    }\n\n    /// Create new branch, can be created only from local branch\n    func newBranch(name: String, from: GitBranch) async throws {\n        try await gitClient.checkoutBranch(from, newName: name)\n        await refreshBranches()\n        await refreshCurrentBranch()\n    }\n\n    /// Rename branch\n    func renameBranch(oldName: String, newName: String) async throws {\n        try await gitClient.renameBranch(oldName: oldName, newName: newName)\n        await refreshBranches()\n    }\n\n    /// Delete branch if it's local and not current\n    func deleteBranch(branch: GitBranch) async throws {\n        if !branch.isLocal || branch == currentBranch {\n            return\n        }\n\n        try await gitClient.deleteBranch(branch)\n        await refreshBranches()\n    }\n\n    /// Delete stash entry\n    func deleteStashEntry(stashEntry: GitStashEntry) async throws {\n        try await gitClient.deleteStashEntry(stashEntry.index)\n        try await refreshStashEntries()\n    }\n\n    /// Apply stash entry\n    func applyStashEntry(stashEntry: GitStashEntry) async throws {\n        try await gitClient.applyStashEntry(stashEntry.index)\n        try await refreshStashEntries()\n        await refreshAllChangedFiles()\n    }\n\n    /// Stash changes\n    func stashChanges(message: String?) async throws {\n        try await gitClient.stash(message: message)\n        try await refreshStashEntries()\n        await refreshAllChangedFiles()\n    }\n\n    /// Delete remote\n    func deleteRemote(remote: GitRemote) async throws {\n        try await gitClient.removeRemote(name: remote.name)\n        try await refreshRemotes()\n    }\n\n    /// Discard changes for file\n    func discardChanges(for file: URL) {\n        Task {\n            do {\n                try await gitClient.discardChanges(for: file)\n                // TODO: Refresh content of active and unmodified document,\n                // requires CodeEditSourceEditor changes\n            } catch {\n                logger.error(\"Failed to discard changes for file (\\(file.lastPathComponent): \\(error)\")\n                await showAlertForError(title: \"Failed to discard changes\", error: error)\n            }\n        }\n    }\n\n    /// Discard changes for repository\n    func discardAllChanges() {\n        Task {\n            do {\n                try await gitClient.discardAllChanges()\n                // TODO: Refresh content of active and unmodified document,\n                // requires CodeEditSourceEditor changes\n            } catch {\n                logger.error(\"Failed to discard changes: \\(error)\")\n                await showAlertForError(title: \"Failed to discard changes\", error: error)\n            }\n        }\n    }\n\n    /// Set changed files on main actor\n    @MainActor\n    private func setChangedFiles(_ files: [GitChangedFile]) {\n        self.changedFiles = files\n    }\n\n    /// Refresh git status for files in project navigator\n    @MainActor\n    private func refreshStatusInFileManager() {\n        guard let fileManager = fileManager else {\n            return\n        }\n\n        var updatedStatusFor: Set<CEWorkspaceFile> = []\n        // Refresh status of file manager files\n        for changedFile in changedFiles {\n            guard let file = fileManager.getFile(changedFile.ceFileKey) else {\n                continue\n            }\n            if file.gitStatus != changedFile.anyStatus() {\n                file.gitStatus = changedFile.anyStatus()\n            }\n            updatedStatusFor.insert(file)\n        }\n\n        for (_, file) in fileManager.flattenedFileItems\n        where !updatedStatusFor.contains(file) && file.gitStatus != nil {\n            file.gitStatus = nil\n            updatedStatusFor.insert(file)\n        }\n\n        if updatedStatusFor.isEmpty {\n            return\n        }\n\n        fileManager.notifyObservers(updatedItems: updatedStatusFor)\n    }\n\n    /// Refresh all changed files and refresh status in file manager\n    func refreshAllChangedFiles() async {\n        do {\n            let status = try await gitClient.getStatus()\n\n            // TODO: Unmerged changes\n            // status.unmergedChanges\n\n            await setChangedFiles(status.changedFiles + status.untrackedFiles)\n            await refreshStatusInFileManager()\n        } catch GitClient.GitClientError.notGitRepository {\n            await setChangedFiles([])\n        } catch {\n            logger.error(\"Error fetching git status: \\(error)\")\n            await setChangedFiles([])\n        }\n    }\n\n    /// Get all changed files for a commit\n    func getCommitChangedFiles(commitSHA: String) async -> [GitChangedFile] {\n        do {\n            return try await gitClient.getCommitChangedFiles(commitSHA: commitSHA)\n        } catch {\n            logger.error(\"Error committing changed files: \\(error)\")\n            return []\n        }\n    }\n\n    /// Commit files selected by user\n    func commit(message: String, details: String? = nil) async throws {\n        try await gitClient.commit(message: message, details: details)\n\n        await self.refreshAllChangedFiles()\n        await self.refreshNumberOfUnsyncedCommits()\n    }\n\n    /// Adds the given URLs to the staged changes.\n    /// - Parameter files: The files to stage.\n    func add(_ files: [URL]) async throws {\n        try await gitClient.add(files)\n    }\n\n    /// Removes the given URLs from the staged changes.\n    /// - Parameter files: The URLs to un-stage.\n    func reset(_ files: [URL]) async throws {\n        try await gitClient.reset(files)\n    }\n\n    /// Refresh number of unsynced commits\n    func refreshNumberOfUnsyncedCommits() async {\n        let numberOfUnpushedCommits = (try? await gitClient.numberOfUnsyncedCommits()) ?? (ahead: 0, behind: 0)\n\n        await MainActor.run {\n            self.numberOfUnsyncedCommits = numberOfUnpushedCommits\n        }\n    }\n\n    /// Add existing remote to git\n    func addRemote(name: String, location: String) async throws {\n        try await gitClient.addRemote(name: name, location: location)\n        try await refreshRemotes()\n    }\n\n    /// Get all remotes\n    func refreshRemotes() async throws {\n        let remotes = (try? await gitClient.getRemotes()) ?? []\n        await MainActor.run {\n            self.remotes = remotes\n        }\n        if !remotes.isEmpty {\n            try await self.refreshAllRemotesBranches()\n        }\n    }\n\n    /// Refresh branches for all remotes\n    func refreshAllRemotesBranches() async throws {\n        for remote in remotes {\n            try await refreshRemoteBranches(remote: remote)\n        }\n    }\n\n    /// Refresh branches for a specific remote\n    func refreshRemoteBranches(remote: GitRemote) async throws {\n        let branches = try await getRemoteBranches(remote: remote.name)\n        if let index = remotes.firstIndex(of: remote) {\n            await MainActor.run {\n                remotes[index].branches = branches\n            }\n        }\n\n    }\n\n    /// Get branches for a specific remote\n    func getRemoteBranches(remote: String) async throws -> [GitBranch] {\n        try await gitClient.getBranches(remote: remote)\n    }\n\n    func refreshStashEntries() async throws {\n        let stashEntries = (try? await gitClient.stashList()) ?? []\n        await MainActor.run {\n            self.stashEntries = stashEntries\n        }\n    }\n\n    /// Pull changes from remote\n    func pull(remote: String? = nil, branch: String? = nil, rebase: Bool = false) async throws {\n        try await gitClient.pullFromRemote(remote: remote, branch: branch, rebase: rebase)\n\n        await self.refreshNumberOfUnsyncedCommits()\n    }\n\n    /// Push changes to remote\n    func push(\n        remote: String? = nil,\n        branch: String? = nil,\n        setUpstream: Bool = false,\n        force: Bool = false,\n        tags: Bool = false\n    ) async throws {\n        guard currentBranch != nil else { return }\n\n        try await gitClient.pushToRemote(\n            remote: remote,\n            branch: branch,\n            setUpstream: setUpstream,\n            force: force,\n            tags: tags\n        )\n\n        await refreshCurrentBranch()\n        await self.refreshNumberOfUnsyncedCommits()\n    }\n\n    /// Initiate repository\n    func initiate() async throws {\n        try await gitClient.initiate()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/SourceControlManager.swift",
    "content": "//\n//  SourceControlModel.swift\n//  CodeEdit\n//\n//  Created by Nanashi Li on 2022/05/20.\n//\n\nimport Foundation\nimport AppKit\nimport OSLog\n\n/// This class is used to perform git functions such as fetch, pull, add/remove of changes, commit, push, etc.\n/// It also stores remotes, branches, current changes, stashes, and commits\nfinal class SourceControlManager: ObservableObject {\n    let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? \"\", category: \"SourceControlManager\")\n\n    let gitClient: GitClient\n\n    /// The base URL of the workspace\n    let workspaceURL: URL\n\n    let editorManager: EditorManager\n    weak var fileManager: CEWorkspaceFileManager?\n\n    /// A list of changed files\n    @Published var changedFiles: [GitChangedFile] = []\n\n    /// Current branch\n    @Published var currentBranch: GitBranch?\n\n    /// All branches, local and remote\n    @Published var branches: [GitBranch] = []\n\n    /// All remotes\n    @Published var remotes: [GitRemote] = []\n\n    /// All stashed entries\n    @Published var stashEntries: [GitStashEntry] = []\n\n    /// Number of unsynced commits with remote in current branch\n    @Published var numberOfUnsyncedCommits: (ahead: Int, behind: Int) = (ahead: 0, behind: 0)\n\n    /// Is project a git repository\n    @Published var isGitRepository: Bool = false\n\n    /// Is the push sheet presented\n    @Published var pushSheetIsPresented: Bool = false {\n        didSet {\n            self.operationBranch = nil\n            self.operationRebase = false\n            self.operationForce = false\n            self.operationIncludeTags = false\n        }\n    }\n\n    /// Is the pull sheet presented\n    @Published var pullSheetIsPresented: Bool = false {\n        didSet {\n            self.operationBranch = nil\n            self.operationRebase = false\n            self.operationForce = false\n            self.operationIncludeTags = false\n        }\n    }\n\n    /// Is the fetch sheet presented\n    @Published var fetchSheetIsPresented: Bool = false\n\n    /// Is the stash sheet presented\n    @Published var stashSheetIsPresented: Bool = false\n\n    /// Is the remote sheet presented\n    @Published var addExistingRemoteSheetIsPresented: Bool = false\n\n    /// Branch selected for source control operations\n    @Published var operationBranch: GitBranch?\n\n    /// Remote selected for source control operations\n    @Published var operationRemote: GitRemote?\n\n    /// Rebase boolean set for source control operations\n    @Published var operationRebase: Bool = false\n\n    /// Force boolean set for source control operations\n    @Published var operationForce: Bool = false\n\n    /// Include tags boolean set for source control operations\n    @Published var operationIncludeTags: Bool = false\n\n    /// Branch to switch to\n    @Published var switchToBranch: GitBranch?\n\n    /// Is discard all alert presented\n    @Published var discardAllAlertIsPresented: Bool = false\n\n    /// Is no changes to stage alert presented\n    @Published var noChangesToStageAlertIsPresented: Bool = false\n\n    /// Is no changes to unstage alert presented\n    @Published var noChangesToUnstageAlertIsPresented: Bool = false\n\n    /// Is no changes to stash alert presented\n    @Published var noChangesToStashAlertIsPresented: Bool = false\n\n    /// Is no changes to discard alert presented\n    @Published var noChangesToDiscardAlertIsPresented: Bool = false\n\n    var orderedLocalBranches: [GitBranch] {\n        var orderedBranches: [GitBranch] = [currentBranch].compactMap { $0 }\n        let otherBranches = branches.filter { $0.isLocal && $0 != currentBranch }\n            .sorted { $0.name.lowercased() < $1.name.lowercased() }\n        orderedBranches.append(contentsOf: otherBranches)\n        return orderedBranches\n    }\n\n    init(\n        workspaceURL: URL,\n        editorManager: EditorManager\n    ) {\n        self.workspaceURL = workspaceURL\n        self.editorManager = editorManager\n        gitClient = GitClient(directoryURL: workspaceURL, shellClient: currentWorld.shellClient)\n    }\n\n    /// Show alert for error\n    func showAlertForError(title: String, error: Error) async {\n        if let error = error as? GitClient.GitClientError {\n            await showAlert(title: title, message: error.description)\n            return\n        }\n\n        if let error = error as? LocalizedError {\n            var description = error.errorDescription ?? \"\"\n            if let failureReason = error.failureReason {\n                if description.isEmpty {\n                    description += failureReason\n                } else {\n                    description += \"\\n\\n\" + failureReason\n                }\n            }\n\n            if let recoverySuggestion = error.recoverySuggestion {\n                if description.isEmpty {\n                    description += recoverySuggestion\n                } else {\n                    description += \"\\n\\n\" + recoverySuggestion\n                }\n            }\n\n            await showAlert(title: title, message: description)\n        } else {\n            await showAlert(title: title, message: error.localizedDescription)\n        }\n    }\n\n    private func showAlert(title: String, message: String) async {\n        await MainActor.run {\n            let alert = NSAlert()\n            alert.messageText = title\n            alert.informativeText = message\n            alert.addButton(withTitle: \"OK\")\n            alert.alertStyle = .warning\n            alert.runModal()\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Views/RemoteBranchPicker.swift",
    "content": "//\n//  RemoteBranchPicker.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 7/1/24.\n//\n\nimport SwiftUI\n\nstruct RemoteBranchPicker: View {\n    @EnvironmentObject var sourceControlManager: SourceControlManager\n\n    @Binding var branch: GitBranch?\n    @Binding var remote: GitRemote?\n\n    let onSubmit: () -> Void\n    let canCreateBranch: Bool\n\n    var shouldCreateBranch: Bool {\n        canCreateBranch && !(remote?.branches.contains(\n            where: { $0.name == (sourceControlManager.currentBranch?.name ?? \"\") }\n        ) ?? true)\n    }\n\n    var body: some View {\n        Group {\n            Picker(selection: $remote) {\n                ForEach(sourceControlManager.remotes, id: \\.name) { remote in\n                    Label {\n                        Text(remote.name)\n                    } icon: {\n                        Image(symbol: \"vault\")\n                    }\n                    .tag(remote as GitRemote?)\n                }\n                Divider()\n                Text(\"Add Existing Remote...\")\n                    .tag(GitRemote?(nil))\n            } label: {\n                Text(\"Remote\")\n            }\n            Picker(selection: $branch) {\n                if shouldCreateBranch {\n                    Label {\n                        Text(\"\\(sourceControlManager.currentBranch?.name ?? \"\") (Create)\")\n                    } icon: {\n                        Image(symbol: \"branch\")\n                    }\n                    .tag(sourceControlManager.currentBranch)\n                }\n                if let branches = remote?.branches, !branches.isEmpty {\n                    ForEach(branches, id: \\.longName) { branch in\n                        Label {\n                            Text(branch.name)\n                        } icon: {\n                            Image(symbol: \"branch\")\n                        }\n                        .tag(branch as GitBranch?)\n                    }\n                }\n            } label: {\n                Text(\"Branch\")\n            }\n        }\n        .onAppear {\n            if remote == nil {\n                updateRemote()\n            }\n        }\n        .onChange(of: remote) { _, newValue in\n            if newValue == nil {\n                sourceControlManager.addExistingRemoteSheetIsPresented = true\n            } else {\n                updateBranch()\n            }\n        }\n    }\n\n    private func updateRemote() {\n        if let currentBranch = sourceControlManager.currentBranch, let upstream = currentBranch.upstream {\n            self.remote = sourceControlManager.remotes.first(where: { upstream.starts(with: $0.name) })\n        } else {\n            self.remote = sourceControlManager.remotes.first\n        }\n    }\n\n    private func updateBranch() {\n        if shouldCreateBranch {\n            self.branch = sourceControlManager.currentBranch\n        } else if let currentBranch = sourceControlManager.currentBranch,\n            let upstream = currentBranch.upstream,\n            let remote = self.remote,\n            let branchIndex = remote.branches.firstIndex(where: { upstream.contains($0.name) }) {\n            self.branch = remote.branches[branchIndex]\n        } else {\n            self.branch = remote?.branches.first\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Views/SourceControlAddExistingRemoteView.swift",
    "content": "//\n//  SourceControlAddExistingRemoteView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/17/23.\n//\n\nimport SwiftUI\n\nstruct SourceControlAddExistingRemoteView: View {\n    @EnvironmentObject var sourceControlManager: SourceControlManager\n    @Environment(\\.dismiss)\n    private var dismiss\n\n    @State private var name: String = \"\"\n    @State private var location: String = \"\"\n\n    enum FocusedField {\n        case name, location\n    }\n\n    @FocusState private var focusedField: FocusedField?\n\n    var body: some View {\n        VStack(spacing: 0) {\n            Form {\n                Section(\"Add Remote\") {\n                    TextField(\"Remote Name\", value: $name, formatter: RegexFormatter(pattern: \"[^a-zA-Z0-9_-]\"))\n                        .focused($focusedField, equals: .name)\n                    TextField(\"Location\", value: $location, formatter: TrimWhitespaceFormatter())\n                        .focused($focusedField, equals: .location)\n                }\n            }\n            .formStyle(.grouped)\n            .scrollDisabled(true)\n            .scrollContentBackground(.hidden)\n            .onSubmit(submit)\n            HStack {\n                Spacer()\n                Button {\n                    dismiss()\n                    name = \"\"\n                    location = \"\"\n                } label: {\n                    Text(\"Cancel\")\n                        .frame(minWidth: 56)\n                }\n                Button {\n                    submit()\n                } label: {\n                    Text(\"Add\")\n                        .frame(minWidth: 56)\n                }\n                .buttonStyle(.borderedProminent)\n            }\n            .padding(.horizontal, 20)\n            .padding(.bottom, 20)\n        }\n        .frame(width: 500)\n        .onAppear {\n            let originExists = sourceControlManager.remotes.contains { $0.name == \"origin\" }\n\n            if !originExists {\n                name = \"origin\"\n                focusedField = .location\n            }\n        }\n    }\n\n    func submit() {\n        Task {\n            do {\n                try await sourceControlManager.addRemote(name: name, location: location)\n                if sourceControlManager.pullSheetIsPresented || sourceControlManager.pushSheetIsPresented {\n                    sourceControlManager.operationRemote = sourceControlManager.remotes.first(\n                        where: { $0.name == name }\n                    )\n                }\n                name = \"\"\n                location = \"\"\n                dismiss()\n            } catch {\n                await sourceControlManager.showAlertForError(title: \"Failed to add remote\", error: error)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Views/SourceControlFetchView.swift",
    "content": "//\n//  SourceControlFetchView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 6/28/24.\n//\n\nimport SwiftUI\n\nstruct SourceControlFetchView: View {\n    @Environment(\\.dismiss)\n    private var dismiss\n\n    @EnvironmentObject var sourceControlManager: SourceControlManager\n    @EnvironmentObject var workspace: WorkspaceDocument\n\n    var projectName: String {\n        workspace.workspaceFileManager?.folderUrl.lastPathComponent ?? \"Empty\"\n    }\n\n    var body: some View {\n        VStack(spacing: 0) {\n            HStack(alignment: .top, spacing: 20) {\n                Image(nsImage: NSApp.applicationIconImage)\n                    .resizable()\n                    .frame(width: 64, height: 64)\n                VStack(alignment: .leading, spacing: 5) {\n                    Text(\"Fetching changes for “\\(projectName)”...\")\n                        .font(.headline)\n                    Text(\"CodeEdit is fetching changes and updating the status of files in the local repository.\")\n                        .font(.subheadline)\n                        .fixedSize(horizontal: false, vertical: true)\n                }\n            }\n            .padding(.top, 20)\n            .padding(.bottom, 10)\n            .padding(.horizontal, 20)\n            HStack {\n                HStack(spacing: 7.5) {\n                    ProgressView()\n                        .progressViewStyle(.circular)\n                        .controlSize(.small)\n                    Text(\"Fetching changes...\")\n                        .font(.subheadline)\n                }\n                Spacer()\n                Button {\n                    dismiss()\n                } label: {\n                    Text(\"Cancel\")\n                        .frame(minWidth: 48)\n                }\n            }\n            .padding(.horizontal, 20)\n            .padding(.bottom, 20)\n        }\n        .frame(width: 420)\n        .task {\n            do {\n                try await sourceControlManager.fetch()\n                dismiss()\n            } catch {\n                await sourceControlManager.showAlertForError(title: \"Failed to fetch changes\", error: error)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Views/SourceControlNewBranchView.swift",
    "content": "//\n//  SourceControlNewBranchView.swift\n//  CodeEdit\n//\n//  Created by Albert Vinizhanau on 10/21/23.\n//\n\nimport SwiftUI\n\nstruct SourceControlNewBranchView: View {\n    @Environment(\\.dismiss)\n    var dismiss\n\n    @EnvironmentObject var sourceControlManager: SourceControlManager\n\n    @State var name: String = \"\"\n    @Binding var fromBranch: GitBranch?\n\n    var body: some View {\n        if let branch = fromBranch ?? sourceControlManager.currentBranch {\n            VStack(spacing: 0) {\n                Form {\n                    Section {\n                        LabeledContent(\n                            \"From\",\n                            value: branch.isRemote\n                                ? branch.longName.replacingOccurrences(of: \"refs/remotes/\", with: \"\")\n                                : branch.name\n                        )\n                        TextField(\"To\", value: $name, formatter: RegexFormatter(pattern: \"[^a-zA-Z0-9_-]\"))\n                    } header: {\n                        Text(\"Create a new branch\")\n                        Text(\n                            \"Create a branch from the current branch and switch to it. \" +\n                            \"All uncommited changes will be preserved on the new branch. \"\n                        )\n                    }\n                }\n                .formStyle(.grouped)\n                .scrollDisabled(true)\n                .scrollContentBackground(.hidden)\n                .onSubmit { submit(branch) }\n                HStack {\n                    Spacer()\n                    Button {\n                        dismiss()\n                    } label: {\n                        Text(\"Cancel\")\n                            .frame(minWidth: 56)\n                    }\n                    Button {\n                        submit(branch)\n                    } label: {\n                        Text(\"Create\")\n                            .frame(minWidth: 56)\n                    }\n                    .buttonStyle(.borderedProminent)\n                    .disabled(name.isEmpty)\n                }\n                .padding(.horizontal, 20)\n                .padding(.top, 12)\n                .padding(.bottom, 20)\n            }\n            .frame(width: 500)\n        }\n    }\n\n    /// Creates a new branch from the specifiied source branch\n    func submit(_ branch: GitBranch) {\n        Task {\n            do {\n                try await sourceControlManager.newBranch(name: name, from: branch)\n                await MainActor.run {\n                    dismiss()\n                }\n            } catch {\n                await sourceControlManager.showAlertForError(\n                    title: \"Failed to create branch\",\n                    error: error\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Views/SourceControlPullView.swift",
    "content": "//\n//  SourceControlPullView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 6/28/24.\n//\n\nimport SwiftUI\n\nstruct SourceControlPullView: View {\n    @Environment(\\.dismiss)\n    private var dismiss\n\n    @EnvironmentObject var sourceControlManager: SourceControlManager\n\n    let gitConfig = GitConfigClient(shellClient: currentWorld.shellClient)\n\n    @State var loading: Bool = false\n\n    @State var preferRebaseWhenPulling: Bool = false\n\n    var body: some View {\n        VStack(spacing: 0) {\n            Form {\n                Section {\n                    RemoteBranchPicker(\n                        branch: $sourceControlManager.operationBranch,\n                        remote: $sourceControlManager.operationRemote,\n                        onSubmit: submit,\n                        canCreateBranch: false\n                    )\n                } header: {\n                    Text(\"Pull remote changes from\")\n                }\n                Section {\n                    Toggle(\"Rebase local changes onto upstream changes\", isOn: $sourceControlManager.operationRebase)\n                }\n            }\n            .formStyle(.grouped)\n            .scrollDisabled(true)\n            .scrollContentBackground(.hidden)\n            .onAppear {\n                Task {\n                    preferRebaseWhenPulling = try await gitConfig.get(key: \"pull.rebase\", global: true) ?? false\n                    if preferRebaseWhenPulling {\n                        sourceControlManager.operationRebase = true\n                    }\n                }\n            }\n            HStack {\n                if loading {\n                    HStack(spacing: 7.5) {\n                        ProgressView()\n                            .progressViewStyle(.circular)\n                            .controlSize(.small)\n                        Text(\"Pulling changes...\")\n                            .font(.subheadline)\n                    }\n                }\n                Spacer()\n                Button {\n                    dismiss()\n                } label: {\n                    Text(\"Cancel\")\n                        .frame(minWidth: 56)\n                }\n                .disabled(loading)\n                Button(action: submit) {\n                    Text(\"Pull\")\n                        .frame(minWidth: 56)\n                }\n                .buttonStyle(.borderedProminent)\n                .disabled(loading)\n            }\n            .padding(.horizontal, 20)\n            .padding(.bottom, 20)\n        }\n        .frame(minWidth: 500)\n    }\n\n    /// Pulls changes from the specified remote and branch. If local changes exist, prompts user to stash them first\n    func submit() {\n        Task {\n            do {\n                if !sourceControlManager.changedFiles.isEmpty {\n                    sourceControlManager.stashSheetIsPresented = true\n                } else {\n                    self.loading = true\n                    try await sourceControlManager.pull(\n                        remote: sourceControlManager.operationRemote?.name ?? nil,\n                        branch: sourceControlManager.operationBranch?.name ?? nil,\n                        rebase: sourceControlManager.operationRebase\n                    )\n                    self.loading = false\n                    dismiss()\n                }\n            } catch {\n                self.loading = false\n                await sourceControlManager.showAlertForError(title: \"Failed to pull\", error: error)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Views/SourceControlPushView.swift",
    "content": "//\n//  SourceControlPushView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 6/26/24.\n//\n\nimport SwiftUI\n\nstruct SourceControlPushView: View {\n    @Environment(\\.dismiss)\n    private var dismiss\n\n    @EnvironmentObject var sourceControlManager: SourceControlManager\n\n    @State var loading: Bool = false\n\n    var body: some View {\n        VStack(spacing: 0) {\n            Form {\n                Section {\n                    RemoteBranchPicker(\n                        branch: $sourceControlManager.operationBranch,\n                        remote: $sourceControlManager.operationRemote,\n                        onSubmit: submit,\n                        canCreateBranch: true\n                    )\n                } header: {\n                    Text(\"Push local changes to\")\n                }\n                Section {\n                    Toggle(\"Force\", isOn: $sourceControlManager.operationForce)\n                    Toggle(\"Include Tags\", isOn: $sourceControlManager.operationIncludeTags)\n                }\n            }\n            .formStyle(.grouped)\n            .scrollDisabled(true)\n            .scrollContentBackground(.hidden)\n            HStack {\n                if loading {\n                    HStack(spacing: 7.5) {\n                        ProgressView()\n                            .progressViewStyle(.circular)\n                            .controlSize(.small)\n                        Text(\"Pushing changes...\")\n                            .font(.subheadline)\n                    }\n                }\n                Spacer()\n                Button {\n                    dismiss()\n                } label: {\n                    Text(\"Cancel\")\n                        .frame(minWidth: 56)\n                }\n                .disabled(loading)\n                Button(action: submit) {\n                    Text(\"Push\")\n                        .frame(minWidth: 56)\n                }\n                .buttonStyle(.borderedProminent)\n                .disabled(loading)\n            }\n            .padding(.horizontal, 20)\n            .padding(.bottom, 20)\n        }\n        .frame(minWidth: 500)\n    }\n\n    /// Pushes commited changes to specified remote and branch\n    func submit() {\n        Task {\n            do {\n                self.loading = true\n                try await sourceControlManager.push(\n                    remote: sourceControlManager.operationRemote?.name ?? nil,\n                    branch: sourceControlManager.operationBranch?.name ?? nil,\n                    setUpstream: sourceControlManager.currentBranch?.upstream == nil,\n                    force: sourceControlManager.operationForce,\n                    tags: sourceControlManager.operationIncludeTags\n                )\n                self.loading = false\n                dismiss()\n            } catch {\n                self.loading = false\n                await sourceControlManager.showAlertForError(title: \"Failed to push\", error: error)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Views/SourceControlRenameBranchView.swift",
    "content": "//\n//  SourceControlRenameBranchView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/28/23.\n//\n\nimport SwiftUI\n\nstruct SourceControlRenameBranchView: View {\n    @Environment(\\.dismiss)\n    var dismiss\n\n    @EnvironmentObject var sourceControlManager: SourceControlManager\n\n    @State var name: String = \"\"\n\n    @Binding var fromBranch: GitBranch?\n\n    var body: some View {\n        if let branch = fromBranch ?? sourceControlManager.currentBranch {\n            VStack(spacing: 0) {\n                Form {\n                    Section {\n                        LabeledContent(\"From\", value: branch.name)\n                        TextField(\"To\", text: $name)\n                    } header: {\n                        Text(\"Rename branch\")\n                        Text(\"All uncommited changes will be preserved on the renamed branch.\")\n                    }\n                }\n                .formStyle(.grouped)\n                .scrollDisabled(true)\n                .scrollContentBackground(.hidden)\n                .onSubmit { submit(branch) }\n                HStack {\n                    Spacer()\n                    Button {\n                        dismiss()\n                    } label: {\n                        Text(\"Cancel\")\n                            .frame(minWidth: 56)\n                    }\n                    Button {\n                        submit(branch)\n                    } label: {\n                        Text(\"Rename\")\n                            .frame(minWidth: 56)\n                    }\n                    .buttonStyle(.borderedProminent)\n                    .disabled(name.isEmpty)\n                }\n                .padding(.horizontal, 20)\n                .padding(.top, 12)\n                .padding(.bottom, 20)\n            }\n            .frame(width: 500)\n        }\n    }\n\n    func submit(_ branch: GitBranch) {\n        Task {\n            do {\n                try await sourceControlManager.renameBranch(oldName: branch.name, newName: name)\n                await MainActor.run {\n                    dismiss()\n                }\n            } catch {\n                await sourceControlManager.showAlertForError(\n                    title: \"Failed to create branch\",\n                    error: error\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Views/SourceControlStashView.swift",
    "content": "//\n//  SourceControlAddRemoteView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/17/23.\n//\n\nimport SwiftUI\n\nstruct SourceControlStashView: View {\n    @EnvironmentObject var sourceControlManager: SourceControlManager\n    @Environment(\\.dismiss)\n    private var dismiss\n\n    @State private var message: String = \"\"\n    @State private var applyStashAfterOperation: Bool = false\n\n    var body: some View {\n        VStack(spacing: 0) {\n            Form {\n                Section {\n                    TextField(\"\", text: $message, prompt: Text(\"Message (optional)\"), axis: .vertical)\n                        .labelsHidden()\n                        .lineLimit(3...3)\n                        .contentShape(Rectangle())\n                        .frame(height: 48)\n                } header: {\n                    Text(\"Stash Changes\")\n                    Group {\n                        if sourceControlManager.pullSheetIsPresented\n                            || sourceControlManager.switchToBranch != nil {\n                            Text(\"Your local repository has uncommitted changes that need to be stashed \" +\n                                 \"before you can continue. Enter a description for your changes.\")\n                        } else {\n                            Text(\"Enter a description for your stashed changes so you can reference them later. \" +\n                                 \"Stashes will appear in the Source Control navigator for your repository.\")\n                        }\n                    }\n                    .multilineTextAlignment(.leading)\n                    .lineLimit(nil)\n                }\n                if sourceControlManager.pullSheetIsPresented\n                    || sourceControlManager.switchToBranch != nil {\n                    Section {\n                        Toggle(\"Apply stash after operation\", isOn: $applyStashAfterOperation)\n                    }\n                }\n            }\n            .formStyle(.grouped)\n            .scrollDisabled(true)\n            .scrollContentBackground(.hidden)\n            .onSubmit(submit)\n            HStack {\n                Spacer()\n                Button {\n                    message = \"\"\n                    dismiss()\n                } label: {\n                    Text(\"Cancel\")\n                        .frame(minWidth: 56)\n                }\n                Button {\n                    submit()\n                } label: {\n                        Text(\n                            sourceControlManager.pullSheetIsPresented\n                            ? \"Stash and Pull\"\n                            : sourceControlManager.switchToBranch != nil\n                            ? \"Stash and Switch\"\n                            : \"Stash\"\n                        )\n                        .frame(minWidth: 56)\n                    }\n                .buttonStyle(.borderedProminent)\n            }\n            .padding(.horizontal, 20)\n            .padding(.bottom, 20)\n        }\n        .frame(width: 500)\n    }\n\n    func submit() {\n        Task {\n            do {\n                try await sourceControlManager.stashChanges(message: message)\n                message = \"\"\n\n                if sourceControlManager.pullSheetIsPresented\n                    || sourceControlManager.switchToBranch != nil {\n                    if sourceControlManager.pullSheetIsPresented {\n                        try await sourceControlManager.pull(\n                            remote: sourceControlManager.operationRemote?.name,\n                            branch: sourceControlManager.operationBranch?.name,\n                            rebase: sourceControlManager.operationRebase\n                        )\n                    }\n\n                    if let branch = sourceControlManager.switchToBranch {\n                        try await sourceControlManager.checkoutBranch(branch: branch)\n                    }\n\n                    if applyStashAfterOperation {\n                        guard let lastStashEntry = sourceControlManager.stashEntries.first else {\n                            throw NSError(\n                                domain: \"SourceControl\",\n                                code: 1,\n                                userInfo: [NSLocalizedDescriptionKey: \"Could not find last stash\"]\n                            )\n                        }\n                        try await sourceControlManager.applyStashEntry(stashEntry: lastStashEntry)\n                    }\n\n                    sourceControlManager.operationRemote = nil\n                    sourceControlManager.operationBranch = nil\n                    sourceControlManager.pullSheetIsPresented = false\n                    sourceControlManager.switchToBranch = nil\n                }\n\n                dismiss()\n            } catch {\n                await sourceControlManager.showAlertForError(title: \"Failed to stash changes\", error: error)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SourceControl/Views/SourceControlSwitchView.swift",
    "content": "//\n//  SourceControlFetchView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 7/9/24.\n//\n\nimport SwiftUI\n\nstruct SourceControlSwitchView: View {\n    @Environment(\\.dismiss)\n    private var dismiss\n\n    @EnvironmentObject var sourceControlManager: SourceControlManager\n    @EnvironmentObject var workspace: WorkspaceDocument\n\n    var branch: GitBranch\n\n    var body: some View {\n        VStack(spacing: 0) {\n            HStack(alignment: .top, spacing: 20) {\n                Image(nsImage: NSApp.applicationIconImage)\n                    .resizable()\n                    .frame(width: 64, height: 64)\n                VStack(alignment: .leading, spacing: 5) {\n                    Text(\"Do you want to switch to “\\(branch.name)”?\")\n                        .font(.headline)\n                    Text(\n                        \"All files in the local repository will switch from the current branch \" +\n                        \"(“\\(sourceControlManager.currentBranch?.name ?? \"\")”) to “\\(branch.name)”.\"\n                    )\n                    .font(.subheadline)\n                    .fixedSize(horizontal: false, vertical: true)\n                }\n                .frame(maxWidth: .infinity, alignment: .leading)\n            }\n            .padding(.top, 20)\n            .padding(.bottom, 10)\n            .padding(.horizontal, 20)\n            HStack {\n                Spacer()\n                Button {\n                    dismiss()\n                } label: {\n                    Text(\"Cancel\")\n                        .frame(minWidth: 56)\n                }\n                Button {\n                    submit()\n                } label: {\n                    Text(\"Switch\")\n                        .frame(minWidth: 56)\n                }\n                .buttonStyle(.borderedProminent)\n            }\n            .padding(.horizontal, 20)\n            .padding(.bottom, 20)\n        }\n        .frame(width: 420)\n    }\n\n    /// Checks out the specifiied branch and if local changes exist prompts the user to shash changes\n    func submit() {\n        Task {\n            do {\n                if !sourceControlManager.changedFiles.isEmpty {\n                    sourceControlManager.stashSheetIsPresented = true\n                } else {\n                    try await sourceControlManager.checkoutBranch(branch: branch)\n                    dismiss()\n                }\n            } catch {\n                await sourceControlManager.showAlertForError(title: \"Failed to checkout\", error: error)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SplitView/Model/CodeEditDividerStyle.swift",
    "content": "//\n//  CodeEditDividerStyle.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 5/30/25.\n//\n\nimport AppKit\n\n/// The style of divider used by ``SplitView``.\n///\n/// To add a new style, add another case to this enum and fill in the ``customColor`` and ``customThickness``\n/// variables. When passed to ``SplitView``, the custom styles will be used instead of the default styles. Leave\n/// values as `nil` to use default styles.\nenum CodeEditDividerStyle: Equatable {\n    case system(NSSplitView.DividerStyle)\n    case editorDivider\n\n    var customColor: NSColor? {\n        switch self {\n        case .system:\n            return nil\n        case .editorDivider:\n            return NSColor(name: nil) { appearance in\n                if appearance.name == .darkAqua {\n                    NSColor.black\n                } else {\n                    NSColor(white: 203.0 / 255.0, alpha: 1.0)\n                }\n            }\n        }\n    }\n\n    var customThickness: CGFloat? {\n        switch self {\n        case .system:\n            return nil\n        case .editorDivider:\n            return 3.0\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SplitView/Model/Environment+ContentInsets.swift",
    "content": "//\n//  Environment+ContentInsets.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 24/02/2023.\n//\n\nimport SwiftUI\n\nstruct EdgeInsetsEnvironmentKey: EnvironmentKey {\n    static var defaultValue: EdgeInsets = EdgeInsets(top: 1, leading: 0, bottom: 0, trailing: 0)\n}\n\nextension EnvironmentValues {\n    var edgeInsets: EdgeInsetsEnvironmentKey.Value {\n        get { self[EdgeInsetsEnvironmentKey.self] }\n        set { self[EdgeInsetsEnvironmentKey.self] = newValue }\n    }\n}\n\nextension EdgeInsets {\n    var nsEdgeInsets: NSEdgeInsets {\n        .init(top: top, left: leading, bottom: bottom, right: trailing)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SplitView/Model/Environment+SplitEditor.swift",
    "content": "//\n//  Environment+SplitEditor.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 16/02/2023.\n//\n\nimport SwiftUI\n\nstruct SplitEditorEnvironmentKey: EnvironmentKey {\n    static var defaultValue: (Edge, Editor) -> Void = { _, _ in }\n}\n\nextension EnvironmentValues {\n    var splitEditor: SplitEditorEnvironmentKey.Value {\n        get { self[SplitEditorEnvironmentKey.self] }\n        set { self[SplitEditorEnvironmentKey.self] = newValue }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SplitView/Model/SplitViewData.swift",
    "content": "//\n//  SplitViewData.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 16/02/2023.\n//\n\nimport SwiftUI\n\nfinal class SplitViewData: ObservableObject {\n    @Published var editorLayouts: [EditorLayout]\n\n    var axis: Axis\n\n    init(_ axis: Axis, editorLayouts: [EditorLayout] = []) {\n        self.editorLayouts = editorLayouts\n        self.axis = axis\n\n        editorLayouts.forEach {\n            if case .one(let editor) = $0 {\n                editor.parent = self\n            }\n        }\n    }\n\n    /// Splits the editor at a certain index into two separate editors.\n    /// - Parameters:\n    ///   - direction: direction in which the editor will be split.\n    ///   If the direction is the same as the ancestor direction,\n    ///   the editor is added to the ancestor instead of creating a new split container.\n    ///   - index: index where the divider will be added.\n    ///   - editor: new editor class that will be used for the editor.\n    func split(_ direction: Edge, at index: Int, new editor: Editor) {\n        editor.parent = self\n        switch (axis, direction) {\n        case (.horizontal, .trailing), (.vertical, .bottom):\n            editorLayouts.insert(.one(editor), at: index+1)\n\n        case (.horizontal, .leading), (.vertical, .top):\n            editorLayouts.insert(.one(editor), at: index)\n\n        case (.horizontal, .top):\n            editorLayouts[index] = .vertical(.init(.vertical, editorLayouts: [.one(editor), editorLayouts[index]]))\n\n        case (.horizontal, .bottom):\n            editorLayouts[index] = .vertical(.init(.vertical, editorLayouts: [editorLayouts[index], .one(editor)]))\n\n        case (.vertical, .leading):\n            editorLayouts[index] = .horizontal(.init(.horizontal, editorLayouts: [.one(editor), editorLayouts[index]]))\n\n        case (.vertical, .trailing):\n            editorLayouts[index] = .horizontal(.init(.horizontal, editorLayouts: [editorLayouts[index], .one(editor)]))\n        }\n    }\n\n    /// Closes an Editor.\n    /// - Parameter id: ID of the Editor.\n    func closeEditor(with id: Editor.ID) {\n        editorLayouts.removeAll { editorLayout in\n            if case .one(let editor) = editorLayout {\n                if editor.id == id {\n                    return true\n                }\n            }\n\n            return false\n        }\n    }\n\n    func getEditorLayout(with id: Editor.ID) -> EditorLayout? {\n        for editorLayout in editorLayouts {\n            if case .one(let editor) = editorLayout {\n                if editor.id == id {\n                    return editorLayout\n                }\n            }\n        }\n\n        return nil\n    }\n\n    /// Flattens the splitviews.\n    func flatten() {\n        for index in editorLayouts.indices {\n            editorLayouts[index].flatten(parent: self)\n        }\n    }\n\n    /// Gets flattened splitviews.\n    func getFlattened() -> [Editor] {\n        var arr: [Editor] = []\n        for index in editorLayouts.indices {\n            arr += editorLayouts[index].getFlattened(parent: self)\n        }\n        return arr\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SplitView/Model/SplitViewItem.swift",
    "content": "//\n//  SplitViewItem.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 05/03/2023.\n//\n\nimport SwiftUI\nimport Combine\n\nclass SplitViewItem: ObservableObject {\n\n    var id: AnyHashable\n    var item: NSSplitViewItem\n\n    var collapsed: Binding<Bool>\n\n    var cancellables: [AnyCancellable] = []\n\n    var observers: [NSKeyValueObservation] = []\n\n    init(child: _VariadicView.Children.Element) {\n        self.id = child.id\n        self.item = NSSplitViewItem(viewController: NSHostingController(rootView: child))\n        self.collapsed = child[SplitViewItemCollapsedViewTraitKey.self]\n        self.item.canCollapse = child[SplitViewItemCanCollapseViewTraitKey.self]\n        self.item.isCollapsed = self.collapsed.wrappedValue\n        self.item.holdingPriority = child[SplitViewHoldingPriorityTraitKey.self]\n        // Skip the initial observation via a dispatch to avoid a \"updating during view update\" error\n        DispatchQueue.main.async {\n            self.observers = self.createObservers()\n        }\n    }\n\n    private func createObservers() -> [NSKeyValueObservation] {\n        [\n            item.observe(\\.isCollapsed) { [weak self] item, _ in\n                self?.collapsed.wrappedValue = item.isCollapsed\n            }\n        ]\n    }\n\n    /// Updates a SplitViewItem.\n    /// This will fetch updated binding values and update them if needed.\n    /// - Parameter child: the view corresponding to the SplitViewItem.\n    func update(child: _VariadicView.Children.Element) {\n        self.item.canCollapse = child[SplitViewItemCanCollapseViewTraitKey.self]\n        let canAnimate = child[SplitViewItemCanAnimateViewTraitKey.self]\n        DispatchQueue.main.async {\n            self.observers = []\n            let collapsed = child[SplitViewItemCollapsedViewTraitKey.self].wrappedValue\n            if canAnimate {\n                self.item.animator().isCollapsed = collapsed\n            } else {\n                self.item.isCollapsed = collapsed\n            }\n            self.item.holdingPriority = child[SplitViewHoldingPriorityTraitKey.self]\n            self.observers = self.createObservers()\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SplitView/Views/SplitView.swift",
    "content": "//\n//  SplitView.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 22/02/2023.\n//\n\nimport SwiftUI\n\nstruct SplitView<Content: View>: View {\n    var axis: Axis\n    var dividerStyle: CodeEditDividerStyle\n    var content: Content\n\n    init(axis: Axis, dividerStyle: CodeEditDividerStyle = .system(.thin), @ViewBuilder content: () -> Content) {\n        self.axis = axis\n        self.dividerStyle = dividerStyle\n        self.content = content()\n    }\n\n    @State private var viewController: () -> SplitViewController? = { nil }\n\n    var body: some View {\n        VStack {\n            content.variadic { children in\n                SplitViewControllerView(\n                    axis: axis,\n                    dividerStyle: dividerStyle,\n                    children: children,\n                    viewController: $viewController\n                )\n            }\n        }\n        ._trait(SplitViewControllerLayoutValueKey.self, viewController)\n        .accessibilityElement(children: .contain)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift",
    "content": "//\n//  SplitViewControllerView.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 20/02/2023.\n//\n\nimport SwiftUI\n\nstruct SplitViewControllerView: NSViewControllerRepresentable {\n\n    var axis: Axis\n    var dividerStyle: CodeEditDividerStyle\n    var children: _VariadicView.Children\n    @Binding var viewController: () -> SplitViewController?\n\n    func makeNSViewController(context: Context) -> SplitViewController {\n        let controller = SplitViewController(axis: axis, parentView: self) { controller in\n            updateItems(controller: controller)\n        }\n        return controller\n    }\n\n    func updateNSViewController(_ controller: SplitViewController, context: Context) {\n        updateItems(controller: controller)\n        controller.setDividerStyle(dividerStyle)\n    }\n\n    private func updateItems(controller: SplitViewController) {\n        var hasChanged = false\n        // Reorder viewcontrollers if needed and add new ones.\n        controller.items = children.map { child in\n            let item: SplitViewItem\n            if let foundItem = controller.items.first(where: { $0.id == child.id }) {\n                item = foundItem\n                item.update(child: child)\n            } else {\n                hasChanged = true\n                item = SplitViewItem(child: child)\n            }\n            return item\n        }\n\n        controller.splitViewItems = controller.items.map(\\.item)\n\n        if hasChanged && controller.splitViewItems.count > 1 {\n            let splitView = controller.splitView\n            let numerator = splitView.isVertical ? splitView.frame.width : splitView.frame.height\n\n            for idx in 0..<controller.items.count-1 {\n                // If the next view is collapsed, don't reposition the divider.\n                guard !controller.items[idx+1].item.isCollapsed else { continue }\n\n                // This method needs to be run twice to ensure the split works correctly if split vertical.\n                // I've absolutely no idea why but it works.\n                splitView.setPosition(\n                    CGFloat(idx + 1) * numerator/CGFloat(controller.items.count),\n                    ofDividerAt: idx\n                )\n                splitView.setPosition(\n                    CGFloat(idx + 1) * numerator/CGFloat(controller.items.count),\n                    ofDividerAt: idx\n                )\n            }\n        }\n    }\n}\n\nfinal class SplitViewController: NSSplitViewController {\n    final class CustomSplitView: NSSplitView {\n        @Invalidating(.display)\n        var customDividerStyle: CodeEditDividerStyle = .system(.thin) {\n            didSet {\n                switch customDividerStyle {\n                case .system(let dividerStyle):\n                    self.dividerStyle = dividerStyle\n                case .editorDivider:\n                    return\n                }\n            }\n        }\n\n        init() {\n            super.init(frame: .zero)\n            dividerStyle = .thin\n        }\n\n        required init?(coder: NSCoder) {\n            fatalError(\"init(coder:) has not been implemented\")\n        }\n\n        override var dividerColor: NSColor {\n            customDividerStyle.customColor ?? super.dividerColor\n        }\n\n        override var dividerThickness: CGFloat {\n            customDividerStyle.customThickness ?? super.dividerThickness\n        }\n\n        override func drawDivider(in rect: NSRect) {\n            let safeRect = NSRect(\n                x: rect.origin.x,\n                y: max(rect.origin.y, safeAreaRect.origin.y),\n                width: isVertical ? dividerThickness : rect.width,\n                height: isVertical ? safeAreaRect.height : dividerThickness\n            )\n            super.drawDivider(in: safeRect)\n        }\n    }\n\n    var items: [SplitViewItem] = []\n    var axis: Axis\n    var parentView: SplitViewControllerView?\n\n    var setUpItems: ((SplitViewController) -> Void)?\n\n    init(axis: Axis, parentView: SplitViewControllerView?, setUpItems: ((SplitViewController) -> Void)?) {\n        self.axis = axis\n        self.parentView = parentView\n        self.setUpItems = setUpItems\n        super.init(nibName: nil, bundle: nil)\n    }\n\n    required init?(coder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n    override func loadView() {\n        splitView = CustomSplitView()\n        super.loadView()\n    }\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n        splitView.isVertical = axis != .vertical\n        setUpItems?(self)\n        DispatchQueue.main.async { [weak self] in\n            self?.parentView?.viewController = { [weak self] in\n                self\n            }\n        }\n    }\n\n    override func splitView(_ splitView: NSSplitView, shouldHideDividerAt dividerIndex: Int) -> Bool {\n        // For some reason, AppKit _really_ wants to hide dividers when there's only one item (and no dividers)\n        // so we do this check for them.\n        guard items.count > 1 else { return false }\n        return super.splitView(splitView, shouldHideDividerAt: dividerIndex)\n    }\n\n    func setDividerStyle(_ dividerStyle: CodeEditDividerStyle) {\n        guard let splitView = splitView as? CustomSplitView else {\n            return\n        }\n        splitView.customDividerStyle = dividerStyle\n    }\n\n    func collapse(for id: AnyHashable, enabled: Bool) {\n        items.first { $0.id == id }?.item.animator().isCollapsed = enabled\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SplitView/Views/SplitViewModifiers.swift",
    "content": "//\n//  SplitViewModifiers.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 05/03/2023.\n//\n\nimport SwiftUI\n\nstruct SplitViewControllerLayoutValueKey: _ViewTraitKey {\n    static var defaultValue: () -> SplitViewController? = { nil }\n}\n\nstruct SplitViewItemCollapsedViewTraitKey: _ViewTraitKey {\n    static var defaultValue: Binding<Bool> = .constant(false)\n}\n\nstruct SplitViewItemCanCollapseViewTraitKey: _ViewTraitKey {\n    static var defaultValue: Bool = false\n}\n\nstruct SplitViewHoldingPriorityTraitKey: _ViewTraitKey {\n    static var defaultValue: NSLayoutConstraint.Priority = .defaultLow\n}\n\nstruct SplitViewItemCanAnimateViewTraitKey: _ViewTraitKey {\n    static var defaultValue: Bool { true }\n}\n\nextension View {\n    func collapsed(_ value: Binding<Bool>) -> some View {\n        self\n        // Use get/set instead of binding directly, so a view update will be triggered if the binding changes.\n            ._trait(SplitViewItemCollapsedViewTraitKey.self, .init {\n                value.wrappedValue\n            } set: {\n                value.wrappedValue = $0\n            })\n    }\n\n    func collapsable() -> some View {\n        self\n            ._trait(SplitViewItemCanCollapseViewTraitKey.self, true)\n    }\n\n    func holdingPriority(_ priority: NSLayoutConstraint.Priority) -> some View {\n        self\n            ._trait(SplitViewHoldingPriorityTraitKey.self, priority)\n    }\n\n    func splitViewCanAnimate(_ enabled: Binding<Bool>) -> some View {\n        self._trait(SplitViewItemCanAnimateViewTraitKey.self, enabled.wrappedValue)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SplitView/Views/SplitViewReader.swift",
    "content": "//\n//  SplitViewReader.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 05/03/2023.\n//\n\nimport SwiftUI\n\nstruct SplitViewReader<Content: View>: View {\n\n    @ViewBuilder var content: (SplitViewProxy) -> Content\n\n    @State private var viewController: () -> SplitViewController? = { nil }\n\n    private var proxy: SplitViewProxy {\n        .init(viewController: viewController)\n    }\n\n    var body: some View {\n        content(proxy)\n            .variadic { children in\n                ForEach(children, id: \\.id) { child in\n                    child\n                        .task(id: child[SplitViewControllerLayoutValueKey.self]()) {\n                            viewController = child[SplitViewControllerLayoutValueKey.self]\n                        }\n                }\n            }\n    }\n}\n\nstruct SplitViewProxy {\n    private var viewController: () -> SplitViewController?\n\n    fileprivate init(viewController: @escaping () -> SplitViewController?) {\n        self.viewController = viewController\n    }\n\n    /// Set the position of a divider in a splitview.\n    /// - Parameters:\n    ///   - index: index of the divider. The mostleft / top divider has index 0.\n    ///   - position: position to place the divider. This is a position inside the views width / height.\n    ///   For example, if the splitview has a width of 500, setting the position to 250\n    ///    will put the divider in the middle of the splitview.\n    func setPosition(of index: Int, position: CGFloat) {\n        viewController()?.splitView.setPosition(position, ofDividerAt: index)\n    }\n\n    /// Collapse a view of the splitview.\n    /// - Parameters:\n    ///   - id: ID of the view\n    ///   - enabled: true for collapse.\n    func collapseView(with id: AnyHashable, _ enabled: Bool) {\n        viewController()?.collapse(for: id, enabled: enabled)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/SplitView/Views/Variadic.swift",
    "content": "//\n//  Variadic.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 05/03/2023.\n//\n\nimport SwiftUI\n\nstruct Helper<Result: View>: _VariadicView_UnaryViewRoot {\n    var _body: (_VariadicView.Children) -> Result\n\n    func body(children: _VariadicView.Children) -> some View {\n        _body(children)\n    }\n}\n\nextension View {\n\n    /// Exposes the children of a ViewBuilder so they can be accessed individually.\n    func variadic<R: View>(@ViewBuilder process: @escaping (_VariadicView.Children) -> R) -> some View {\n        _VariadicView.Tree(Helper(_body: process), content: { self })\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/StatusBar/Models/ImageDimensions.swift",
    "content": "//\n//  ImageDimensions.swift\n//  CodeEdit\n//\n//  Created by Paul Ebose on 2024/5/13.\n//\n\nimport Foundation\n\n/// Helper struct used to store the (width x height) of the currently opened image.\nstruct ImageDimensions {\n    var width: Int\n    var height: Int\n}\n"
  },
  {
    "path": "CodeEdit/Features/StatusBar/ViewModels/StatusBarViewModel.swift",
    "content": "//\n//  StatusBarViewModel.swift\n//  CodeEdit\n//\n//  Created by Paul Ebose on 2024/5/12.\n//\n\nimport SwiftUI\n\nfinal class StatusBarViewModel: ObservableObject {\n\n    /// The file size of the currently opened file.\n    @Published var fileSize: Int?\n\n    /// The dimensions (width x height) of the currently opened image.\n    @Published var dimensions: ImageDimensions?\n\n    /// Indicates whether the breakpoint is enabled or not.\n    @Published var isBreakpointEnabled = true\n\n    /// The font style of items shown in the status bar.\n    private(set) var statusBarFont = Font.system(size: 11, weight: .medium)\n\n    /// The color of the text shown in the status bar.\n    private(set) var foregroundStyle = Color.secondary\n\n}\n"
  },
  {
    "path": "CodeEdit/Features/StatusBar/ViewModifiers/UpdateStatusBarInfo.swift",
    "content": "//\n//  UpdateStatusBarInfo.swift\n//  CodeEdit\n//\n//  Created by Paul Ebose on 2024/5/12.\n//\n\nimport SwiftUI\n\n/// Updates ``StatusBarFileInfoView``'s `fileSize` and `dimensions`.\n/// ```swift\n/// FileView\n///   .modifier(UpdateStatusBarInfo(withURL))\n/// ```\nstruct UpdateStatusBarInfo: ViewModifier {\n\n    /// The URL of the file to compute information from.\n    let fileURL: URL?\n\n    init(with fileURL: URL?) {\n        self.fileURL = fileURL\n    }\n\n    @EnvironmentObject private var editorManager: EditorManager\n    @EnvironmentObject private var statusBarViewModel: StatusBarViewModel\n\n    /// This is returned by ``UpdateStatusBarInfo`` `.computeStatusBarInfo`.\n    private struct ComputedStatusBarInfo {\n        let fileSize: Int\n        let dimensions: ImageDimensions?\n    }\n\n    /// Compute information that can be used to update properties in ``StatusBarFileInfoView``.\n    /// - Parameter with fileURL: URL of the file to compute information from.\n    /// - Returns: The file size and its image dimensions (if any).\n    private func computeStatusBarInfo(with fileURL: URL) -> ComputedStatusBarInfo? {\n        guard let resourceValues = try? fileURL.resourceValues(forKeys: [.contentTypeKey, .fileSizeKey]),\n              let contentType = resourceValues.contentType,\n              let fileSize = resourceValues.fileSize\n        else {\n            return nil\n        }\n\n        if contentType.conforms(to: .image), let imageReps = NSImage(contentsOf: fileURL)?.representations.first {\n            let dimensions = ImageDimensions(width: imageReps.pixelsWide, height: imageReps.pixelsHigh)\n            return ComputedStatusBarInfo(fileSize: fileSize, dimensions: dimensions)\n        } else { // non-image file\n            return ComputedStatusBarInfo(fileSize: fileSize, dimensions: nil)\n        }\n    }\n\n    func body(content: Content) -> some View {\n        if let fileURL {\n            content\n                .onAppear {\n                    let statusBarInfo = computeStatusBarInfo(with: fileURL)\n                    statusBarViewModel.fileSize = statusBarInfo?.fileSize\n                    statusBarViewModel.dimensions = statusBarInfo?.dimensions\n                }\n                .onChange(of: editorManager.activeEditor.selectedTab) { _, newTab in\n                    guard let newTab else { return }\n                    let statusBarInfo = computeStatusBarInfo(with: newTab.file.url)\n                    statusBarViewModel.fileSize = statusBarInfo?.fileSize\n                    statusBarViewModel.dimensions = statusBarInfo?.dimensions\n                }\n        } else {\n            content\n        }\n    }\n\n}\n"
  },
  {
    "path": "CodeEdit/Features/StatusBar/Views/StatusBarIcon.swift",
    "content": "//\n//  StatusBarIcon.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 3/23/23.\n//\n\nimport SwiftUI\n\n/// Accessory icon view for status bar.\nstruct StatusBarIcon: View {\n    /// Unifies icon font for status bar accessories.\n    private let iconFont: Font\n\n    enum IconSize: CGFloat {\n        case small = 11\n        case medium = 14.5\n    }\n\n    private let icon: Image\n    private let active: Bool\n    private let action: () -> Void\n\n    init(icon: Image, size: IconSize = .medium, active: Bool? = false, action: @escaping () -> Void) {\n        self.icon = icon\n        self.action = action\n        self.active = active ?? false\n        self.iconFont = Font.system(size: size.rawValue, weight: .regular, design: .default)\n    }\n\n    var body: some View {\n        Button(\n            action: action,\n            label: {\n                icon\n                    .font(iconFont)\n                    .contentShape(Rectangle())\n            }\n        )\n        .buttonStyle(StatusBarIconButtonStyle(isActive: active))\n    }\n}\n\nstruct StatusBarIconButtonStyle: ButtonStyle {\n    var isActive: Bool = false\n    func makeBody(configuration: Self.Configuration) -> some View {\n        configuration.label\n            .foregroundColor(isActive ? Color.accentColor : Color.secondary)\n            .brightness(configuration.isPressed ? 0.5 : 0)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarBreakpointButton.swift",
    "content": "//\n//  StatusBarBreakpointButton.swift\n//  CodeEdit\n//\n//  Created by Stef Kors on 14/04/2022.\n//\n\nimport SwiftUI\nimport CodeEditSymbols\n\nstruct StatusBarBreakpointButton: View {\n    @EnvironmentObject private var statusBarViewModel: StatusBarViewModel\n\n    var body: some View {\n        Button {\n            statusBarViewModel.isBreakpointEnabled.toggle()\n        } label: {\n            if statusBarViewModel.isBreakpointEnabled {\n                Image.breakpointFill\n                    .foregroundColor(.accentColor)\n            } else {\n                Image.breakpoint\n                    .foregroundColor(.secondary)\n            }\n        }\n        .buttonStyle(.plain)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarCursorPositionLabel.swift",
    "content": "//\n//  StatusBarCursorPositionLabel.swift\n//  CodeEdit\n//\n//  Created by Lukas Pistrol on 22.03.22.\n//\n\nimport SwiftUI\nimport Combine\nimport CodeEditSourceEditor\n\nstruct StatusBarCursorPositionLabel: View {\n    @EnvironmentObject private var utilityAreaViewModel: UtilityAreaViewModel\n    @EnvironmentObject private var editorManager: EditorManager\n\n    @State private var tab: EditorInstance?\n\n    /// Updates the source of cursor position notifications.\n    func updateSource() {\n        tab = editorManager.activeEditor.selectedTab\n    }\n\n    var body: some View {\n        Group {\n            if let currentTab = tab {\n                LineLabel(editorInstance: currentTab)\n            } else {\n                Text(\"\").accessibilityLabel(\"No Selection\")\n            }\n        }\n        .fixedSize()\n        .accessibilityIdentifier(\"CursorPositionLabel\")\n        .accessibilityAddTraits(.updatesFrequently)\n        .onHover { isHovering($0) }\n        .onAppear {\n            updateSource()\n        }\n        .onReceive(editorManager.tabBarTabIdSubject) { _ in\n            updateSource()\n        }\n    }\n\n    struct LineLabel: View {\n        @Environment(\\.modifierKeys)\n        private var modifierKeys\n        @Environment(\\.controlActiveState)\n        private var controlActive\n\n        @EnvironmentObject private var statusBarViewModel: StatusBarViewModel\n\n        let editorInstance: EditorInstance\n\n        @State private var cursorPositions: [CursorPosition] = []\n\n        init(editorInstance: EditorInstance) {\n            self.editorInstance = editorInstance\n        }\n\n        var body: some View {\n            Text(getLabel())\n                .font(statusBarViewModel.statusBarFont)\n                .foregroundColor(foregroundColor)\n                .lineLimit(1)\n                .onReceive(editorInstance.$cursorPositions) { newValue in\n                    self.cursorPositions = newValue\n                }\n        }\n\n        private var foregroundColor: Color {\n            if controlActive == .inactive {\n                Color(nsColor: .disabledControlTextColor)\n            } else {\n                Color(nsColor: .secondaryLabelColor)\n            }\n        }\n\n        /// Finds the lines contained by a range in the currently selected document.\n        /// - Parameter range: The range to query.\n        /// - Returns: The number of lines in the range.\n        func getLines(_ range: NSRange) -> Int {\n            return editorInstance.rangeTranslator.linesInRange(range)\n        }\n\n        /// Create a label string for cursor positions.\n        /// - Returns: A string describing the user's location in a document.\n        func getLabel() -> String {\n            if cursorPositions.isEmpty {\n                return \"\"\n            }\n\n            // More than one selection, display the number of selections.\n            if cursorPositions.count > 1 {\n                return \"\\(cursorPositions.count) selected ranges\"\n            }\n\n            // If the selection is more than just a cursor, return the length.\n            if cursorPositions[0].range.length > 0 {\n                // When the option key is pressed display the character range.\n                if modifierKeys.contains(.option) {\n                    return \"Char: \\(cursorPositions[0].range.location) Len: \\(cursorPositions[0].range.length)\"\n                }\n\n                let lineCount = getLines(cursorPositions[0].range)\n\n                if lineCount > 1 {\n                    return \"\\(lineCount) lines\"\n                }\n\n                return \"\\(cursorPositions[0].range.length) characters\"\n            }\n\n            // When the option key is pressed display the character offset.\n            if modifierKeys.contains(.option) {\n                return \"Char: \\(cursorPositions[0].range.location) Len: 0\"\n            }\n\n            // When there's a single cursor, display the line and column.\n            return \"Line: \\(cursorPositions[0].start.line)  Col: \\(cursorPositions[0].start.column)\"\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarEncodingSelector.swift",
    "content": "//\n//  StatusBarEncodingSelector.swift\n//  CodeEditModules/StatusBar\n//\n//  Created by Lukas Pistrol on 22.03.22.\n//\n\nimport SwiftUI\n\nstruct StatusBarEncodingSelector: View {\n\n    var body: some View {\n        Menu {\n            // UTF 8, ASCII, ...\n        } label: {\n            Text(\"UTF 8\")\n        }\n        .menuStyle(StatusBarMenuStyle())\n        .onHover { isHovering($0) }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarFileInfoView.swift",
    "content": "//\n//  StatusBarFileInfoView.swift\n//  CodeEdit\n//\n//  Created by Paul Ebose on 2024/5/12.\n//\n\nimport SwiftUI\n\n/// Shows media information about the currently opened file.\n///\n/// This currently shows the file size and image dimensions, if available.\nstruct StatusBarFileInfoView: View {\n\n    @EnvironmentObject private var statusBarViewModel: StatusBarViewModel\n\n    private let dimensionsNumberStyle = IntegerFormatStyle<Int>(locale: Locale(identifier: \"en_US\")).grouping(.never)\n\n    var body: some View {\n\n        HStack(spacing: 15) {\n\n            if let dimensions = statusBarViewModel.dimensions {\n                let width = dimensionsNumberStyle.format(dimensions.width)\n                let height = dimensionsNumberStyle.format(dimensions.height)\n\n                Text(\"\\(width) × \\(height)\")\n            }\n\n            if let fileSize = statusBarViewModel.fileSize {\n                Text(fileSize.formatted(.byteCount(style: .memory)))\n            }\n\n        }\n        .font(statusBarViewModel.statusBarFont)\n        .foregroundStyle(statusBarViewModel.foregroundStyle)\n    }\n\n}\n"
  },
  {
    "path": "CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarIndentSelector.swift",
    "content": "//\n//  StatusBarIndentSelector.swift\n//  CodeEditModules/StatusBar\n//\n//  Created by Lukas Pistrol on 22.03.22.\n//\n\nimport SwiftUI\n\nstruct StatusBarIndentSelector: View {\n    @AppSettings(\\.textEditing.defaultTabWidth)\n    var defaultTabWidth\n\n    var body: some View {\n        Menu {\n            Button {} label: {\n                Text(\"Use Tabs\")\n            }.disabled(true)\n\n            Button {} label: {\n                Text(\"Use Spaces\")\n            }.disabled(true)\n\n            Divider()\n\n            Picker(\"Tab Width\", selection: $defaultTabWidth) {\n                ForEach(2..<9) { index in\n                    Text(\"\\(index) Spaces\")\n                        .tag(index)\n                }\n            }\n        } label: {\n            Text(\"\\(defaultTabWidth) Spaces\")\n        }\n        .menuStyle(StatusBarMenuStyle())\n        .onHover { isHovering($0) }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarLineEndSelector.swift",
    "content": "//\n//  StatusBarLineEndSelector.swift\n//  CodeEditModules/StatusBar\n//\n//  Created by Lukas Pistrol on 22.03.22.\n//\n\nimport SwiftUI\n\nstruct StatusBarLineEndSelector: View {\n\n    var body: some View {\n        Menu {\n            // LF, CRLF\n        } label: {\n            Text(\"LF\")\n        }\n        .menuStyle(StatusBarMenuStyle())\n        .onHover { isHovering($0) }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarMenuStyle.swift",
    "content": "//\n//  StatusBarMenuStyle.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 3/24/23\n//\n\nimport SwiftUI\nimport CodeEditSymbols\n\nstruct StatusBarMenuStyle: MenuStyle {\n    @Environment(\\.controlActiveState)\n    private var controlActive\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    func makeBody(configuration: Configuration) -> some View {\n        Menu(configuration)\n            .controlSize(.small)\n            .menuStyle(.borderlessButton)\n            .opacity(controlActive == .inactive\n                ? colorScheme == .dark ? 0.66 : 1\n                : colorScheme == .dark ? 0.54 : 0.72)\n            .fixedSize()\n    }\n}\n\nextension MenuStyle where Self == StatusBarMenuStyle {\n    static var statusBar: StatusBarMenuStyle { .init() }\n}\n"
  },
  {
    "path": "CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarToggleUtilityAreaButton.swift",
    "content": "//\n//  StatusBarToggleUtilityAreaButton.swift\n//  CodeEdit\n//\n//  Created by Lukas Pistrol on 22.03.22.\n//\n\nimport SwiftUI\n\ninternal struct StatusBarToggleUtilityAreaButton: View {\n    @Environment(\\.controlActiveState)\n    var controlActiveState\n\n    @EnvironmentObject private var utilityAreaViewModel: UtilityAreaViewModel\n\n    internal var body: some View {\n        Button {\n            utilityAreaViewModel.togglePanel()\n        } label: {\n            Image(systemName: \"square.bottomthird.inset.filled\")\n        }\n        .buttonStyle(.icon)\n        .keyboardShortcut(\"Y\", modifiers: [.command, .shift])\n        .help(utilityAreaViewModel.isCollapsed ? \"Show the Utility area\" : \"Hide the Utility area\")\n        .onHover { isHovering($0) }\n        .onChange(of: controlActiveState) { _, newValue in\n            if newValue == .key {\n                CommandManager.shared.addCommand(\n                    name: \"Toggle Utility Area\",\n                    title: \"Toggle Utility Area\",\n                    id: \"open.drawer\",\n                    command: { [weak utilityAreaViewModel] in utilityAreaViewModel?.togglePanel() }\n                )\n            }\n        }\n        .onAppear {\n            CommandManager.shared.addCommand(\n                name: \"Toggle Utility Area\",\n                title: \"Toggle Utility Area\",\n                id: \"open.drawer\",\n                command: { [weak utilityAreaViewModel] in utilityAreaViewModel?.togglePanel() }\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/StatusBar/Views/StatusBarView.swift",
    "content": "//\n//  StatusBarView.swift\n//  CodeEdit\n//\n//  Created by Lukas Pistrol on 19.03.22.\n//\n\nimport SwiftUI\n\n/// # StatusBarView\n///\n/// A View that lives on the bottom of the window and offers information\n/// about compilation errors/warnings, git,  cursor position in text,\n/// indentation width (in spaces), text encoding and linebreak.\n///\n/// Also information about the file size and dimensions, if available.\n///\n/// Additionally it offers a togglable/resizable drawer which can\n/// host a terminal or additional debug information\n///\nstruct StatusBarView: View {\n    @Environment(\\.controlActiveState)\n    private var controlActive\n\n    static let height = 28.0\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    var proxy: SplitViewProxy\n\n    static let statusbarID = \"statusbarID\"\n\n    /// The actual status bar\n    var body: some View {\n        HStack(alignment: .center, spacing: 10) {\n//            StatusBarBreakpointButton()\n//            StatusBarDivider()\n            Spacer()\n            StatusBarFileInfoView()\n            StatusBarCursorPositionLabel()\n            StatusBarDivider()\n            StatusBarToggleUtilityAreaButton()\n        }\n        .padding(.horizontal, 10)\n        .cursor(.resizeUpDown)\n        .frame(height: Self.height)\n        .background(.bar)\n        .padding(.top, 1)\n        .overlay(alignment: .top) {\n            Divider()\n                .overlay(Color(nsColor: colorScheme == .dark ? .black : .clear))\n        }\n        .gesture(dragGesture)\n        .disabled(controlActive == .inactive)\n    }\n\n    /// A drag gesture to resize the drawer beneath the status bar\n    private var dragGesture: some Gesture {\n        DragGesture(coordinateSpace: .global)\n            .onChanged { value in\n                proxy.setPosition(of: 0, position: value.location.y + Self.height / 2)\n            }\n    }\n}\n\nstruct StatusBarDivider: View {\n    var body: some View {\n        Divider()\n            .frame(maxHeight: 12)\n//            .padding(.horizontal, 7)\n    }\n}\n\nextension View {\n    func cursor(_ cursor: NSCursor) -> some View {\n        onHover {\n            if $0 {\n                cursor.push()\n            } else {\n                cursor.pop()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Tasks/Models/CEActiveTask.swift",
    "content": "//\n//  CEActiveTask.swift\n//  CodeEditTests\n//\n//  Created by Tommy Ludwig on 24.06.24.\n//\n\nimport SwiftUI\nimport Combine\nimport SwiftTerm\n\n/// Stores the state of a task once it's executed\nclass CEActiveTask: ObservableObject, Identifiable, Hashable {\n    /// The current progress of the task.\n    @Published var output: CEActiveTaskTerminalView?\n\n    var hasOutputBeenConfigured: Bool = false\n\n    /// The status of the task.\n    @Published private(set) var status: CETaskStatus = .notRunning\n\n    /// The name of the associated task.\n    @ObservedObject var task: CETask\n\n    /// Prevents tasks overwriting each other.\n    /// Say a user cancels one task, then runs it immediately, the cancel message should show and then the\n    /// starting message should show. If we don't add this modifier the starting message will be deleted.\n    var activeTaskID: UUID = UUID()\n\n    var taskId: String {\n        task.id.uuidString + \"-\" + activeTaskID.uuidString\n    }\n\n    var workspaceURL: URL?\n\n    private var cancellables = Set<AnyCancellable>()\n\n    init(task: CETask) {\n        self.task = task\n\n        self.task.objectWillChange.sink { _ in\n            self.objectWillChange.send()\n        }.store(in: &cancellables)\n    }\n\n    @MainActor\n    func run(workspaceURL: URL?, shell: Shell? = nil) {\n        self.workspaceURL = workspaceURL\n        self.activeTaskID = UUID() // generate a new ID for this run\n\n        createStatusTaskNotification()\n        updateTaskStatus(to: .running)\n\n        let view = output ?? CEActiveTaskTerminalView(activeTask: self)\n        view.startProcess(workspaceURL: workspaceURL, shell: shell)\n\n        output = view\n    }\n\n    @MainActor\n    func handleProcessFinished(terminationStatus: Int32) {\n        // Shells add 128 to non-zero exit codes.\n        var terminationStatus = terminationStatus\n        if terminationStatus > 128 {\n            terminationStatus -= 128\n        }\n\n        switch terminationStatus {\n        case 0:\n            output?.newline()\n            output?.sendOutputMessage(\"Finished running \\(task.name).\")\n            output?.newline()\n\n            updateTaskStatus(to: .finished)\n            updateTaskNotification(\n                title: \"Finished Running \\(task.name)\",\n                message: \"\",\n                isLoading: false\n            )\n        case 2, 15: // SIGINT or SIGTERM\n            output?.newline()\n            output?.sendOutputMessage(\"\\(task.name) cancelled.\")\n            output?.newline()\n\n            updateTaskStatus(to: .notRunning)\n            updateTaskNotification(\n                title: \"\\(task.name) cancelled\",\n                message: \"\",\n                isLoading: false\n            )\n        case 17: // SIGSTOP\n            updateTaskStatus(to: .stopped)\n        default:\n            output?.newline()\n            output?.sendOutputMessage(\"Failed to run \\(task.name)\")\n            output?.newline()\n\n            updateTaskStatus(to: .failed)\n            updateTaskNotification(\n                title: \"Failed Running \\(task.name)\",\n                message: \"\",\n                isLoading: false\n            )\n        }\n\n        deleteStatusTaskNotification()\n    }\n\n    @MainActor\n    func suspend() {\n        if let shellPID = output?.runningPID(), status == .running {\n            kill(shellPID, SIGSTOP)\n            updateTaskStatus(to: .stopped)\n        }\n    }\n\n    @MainActor\n    func resume() {\n        if let shellPID = output?.runningPID(), status == .running {\n            kill(shellPID, SIGCONT)\n            updateTaskStatus(to: .running)\n        }\n    }\n\n    func terminate() {\n        if let shellPID = output?.runningPID() {\n            kill(shellPID, SIGTERM)\n        }\n    }\n\n    func interrupt() {\n        if let shellPID = output?.runningPID() {\n            kill(shellPID, SIGINT)\n        }\n    }\n\n    func waitForExit() {\n        if let shellPID = output?.runningPID() {\n            waitid(P_PGID, UInt32(shellPID), nil, 0)\n        }\n    }\n\n    @MainActor\n    func clearOutput() {\n        output?.terminal.resetToInitialState()\n        output?.feed(text: \"\")\n    }\n\n    private func createStatusTaskNotification() {\n        let userInfo: [String: Any] = [\n            \"id\": taskId,\n            \"action\": \"createWithPriority\",\n            \"title\": \"Running \\(self.task.name)\",\n            \"message\": \"Running your task: \\(self.task.name).\",\n            \"isLoading\": true,\n            \"workspace\": workspaceURL as Any\n        ]\n\n        NotificationCenter.default.post(name: .taskNotification, object: nil, userInfo: userInfo)\n    }\n\n    private func deleteStatusTaskNotification() {\n        let deleteInfo: [String: Any] = [\n            \"id\": taskId,\n            \"action\": \"deleteWithDelay\",\n            \"delay\": 3.0,\n            \"workspace\": workspaceURL as Any\n        ]\n\n        NotificationCenter.default.post(name: .taskNotification, object: nil, userInfo: deleteInfo)\n    }\n\n    private func updateTaskNotification(title: String? = nil, message: String? = nil, isLoading: Bool? = nil) {\n        var userInfo: [String: Any] = [\n            \"id\": taskId,\n            \"action\": \"update\",\n            \"workspace\": workspaceURL as Any\n        ]\n        if let title {\n            userInfo[\"title\"] = title\n        }\n        if let message {\n            userInfo[\"message\"] = message\n        }\n        if let isLoading {\n            userInfo[\"isLoading\"] = isLoading\n        }\n\n        NotificationCenter.default.post(name: .taskNotification, object: nil, userInfo: userInfo)\n    }\n\n    @MainActor\n    func updateTaskStatus(to taskStatus: CETaskStatus) {\n        self.status = taskStatus\n    }\n\n    static func == (lhs: CEActiveTask, rhs: CEActiveTask) -> Bool {\n        return lhs.output == rhs.output &&\n        lhs.status == rhs.status &&\n        lhs.output?.process.shellPid == rhs.output?.process.shellPid &&\n        lhs.task == rhs.task\n    }\n\n    func hash(into hasher: inout Hasher) {\n        hasher.combine(output)\n        hasher.combine(status)\n        hasher.combine(task)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Tasks/Models/CETaskStatus.swift",
    "content": "//\n//  CETaskStatus.swift\n//  CodeEditTests\n//\n//  Created by Tommy Ludwig on 24.06.24.\n//\n\nimport SwiftUI\n\n/// Enum to represent a task's status\nenum CETaskStatus {\n    // default state\n    case notRunning\n    // User suspended the process\n    case stopped\n    case running\n    // Processes finished with an error\n    case failed\n    // Processes finished without an error\n    case finished\n\n    var color: Color {\n        switch self {\n        case .notRunning: return Color.gray\n        case .stopped: return Color.yellow\n        case .running: return Color.orange\n        case .failed: return Color.red\n        case .finished: return Color.green\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Tasks/TaskManager.swift",
    "content": "//\n//  TaskManager.swift\n//  CodeEditTests\n//\n//  Created by Tommy Ludwig on 24.06.24.\n//\n\nimport SwiftUI\nimport Combine\n\n/// This class handles the execution of tasks\n@MainActor\nclass TaskManager: ObservableObject {\n    @Published var activeTasks: [UUID: CEActiveTask] = [:]\n    @Published var selectedTaskID: UUID?\n    @Published var taskShowingOutput: UUID?\n\n    @ObservedObject var workspaceSettings: CEWorkspaceSettingsData\n\n    private var workspaceURL: URL?\n    private var settingsListener: AnyCancellable?\n\n    init(workspaceSettings: CEWorkspaceSettingsData, workspaceURL: URL?) {\n        self.workspaceURL = workspaceURL\n        self.workspaceSettings = workspaceSettings\n\n        settingsListener = workspaceSettings.$tasks\n            .receive(on: DispatchQueue.main)\n            .sink { [weak self] _ in\n                self?.updateSelectedTaskID()\n            }\n    }\n\n    var selectedTask: CETask? {\n        if let selectedTaskID {\n            return availableTasks.first { $0.id == selectedTaskID }\n        } else {\n            if let newSelectedTask = availableTasks.first {\n                Task {\n                    await MainActor.run {\n                        self.selectedTaskID = newSelectedTask.id\n                    }\n                }\n                return newSelectedTask\n            }\n        }\n        return nil\n    }\n\n    var availableTasks: [CETask] {\n        return workspaceSettings.tasks\n    }\n\n    func taskStatus(taskID: UUID) -> CETaskStatus {\n        return self.activeTasks[taskID]?.status ?? .notRunning\n    }\n\n    func updateSelectedTaskID() {\n        guard selectedTask == nil else { return }\n        selectedTaskID = availableTasks.first?.id\n    }\n\n    func executeActiveTask() {\n        guard let task = workspaceSettings.tasks.first(where: { $0.id == selectedTaskID }) else { return }\n        Task {\n            await runTask(task: task)\n        }\n    }\n\n    func runTask(task: CETask) async {\n        // A process can only be started once, that means we have to renew the Process and Pipe\n        // but don't initialize a new object.\n        if let activeTask = activeTasks[task.id] {\n            activeTask.terminate()\n            // Wait until the task is no longer running.\n            // The termination handler is asynchronous, so we avoid a race condition using this.\n            while activeTask.status == .running {\n                await Task.yield()\n            }\n            activeTask.run(workspaceURL: workspaceURL)\n        } else {\n            let runningTask = CEActiveTask(task: task)\n            runningTask.run(workspaceURL: workspaceURL)\n            await MainActor.run {\n                activeTasks[task.id] = runningTask\n            }\n        }\n    }\n\n    func terminateActiveTask() {\n        guard let taskID = selectedTaskID else {\n            return\n        }\n\n        terminateTask(taskID: taskID)\n    }\n\n    /// Suspends the task associated with the given task ID.\n    ///\n    /// Suspending a task means that the task's execution is paused.\n    /// The task will not run or consume CPU time until it is resumed.\n    /// If there is no task associated with the given ID, or if the task is not currently running,\n    /// this method does nothing.\n    ///\n    /// - Parameter taskID: The ID of the task to suspend.\n    func suspendTask(taskID: UUID) {\n        if let activeTask = activeTasks[taskID] {\n            activeTask.suspend()\n        }\n    }\n\n    /// Resumes the task associated with the given task ID.\n    ///\n    /// If there is no task associated with the given ID, or if the task is not currently suspended,\n    /// this method does nothing.\n    ///\n    /// - Parameter taskID: The ID of the task to resume.\n    func resumeTask(taskID: UUID) {\n        if let activeTask = activeTasks[taskID] {\n            activeTask.resume()\n        }\n    }\n\n    /// Terminates the task associated with the given task ID.\n    ///\n    /// Terminating a task sends a SIGTERM signal to the process, which is a request to the process to stop execution.\n    /// Most processes will stop when they receive a SIGTERM signal.\n    /// However, a process can choose to ignore this signal.\n    ///\n    /// If there is no task associated with the given ID,\n    /// or if the task is not currently running, this method does nothing.\n    ///\n    /// - Parameter taskID: The ID of the task to terminate.\n    func terminateTask(taskID: UUID) {\n        if let activeTask = activeTasks[taskID] {\n            activeTask.terminate()\n        }\n    }\n\n    /// Interrupts the task associated with the given task ID.\n    ///\n    /// Interrupting a task sends a SIGINT signal to the process, which is a request to the process to stop execution.\n    /// This is the same signal that is sent when you press Ctrl+C in a terminal.\n    /// It's a polite request to the process to stop what it's doing and terminate.\n    /// However, the process can choose to ignore this signal or handle it in a custom way.\n    ///\n    /// If there is no task associated with the given ID, or if the task is not currently running,\n    /// this method does nothing.\n    ///\n    /// - Parameter taskID: The ID of the task to interrupt.\n    func interruptTask(taskID: UUID) {\n        if let activeTask = activeTasks[taskID] {\n            activeTask.interrupt()\n        }\n    }\n\n    func stopAllTasks() {\n        for (id, _) in activeTasks {\n            interruptTask(taskID: id)\n        }\n    }\n\n    func deleteTask(taskID: UUID) {\n        terminateTask(taskID: taskID)\n        activeTasks.removeValue(forKey: taskID)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Tasks/ToolbarItems/StartTaskToolbarItem.swift",
    "content": "//\n//  StartTaskToolbarItem.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 8/28/25.\n//\n\nimport AppKit\n\n@available(macOS 26, *)\nfinal class StartTaskToolbarItem: NSToolbarItem {\n    private weak var workspace: WorkspaceDocument?\n\n    private var utilityAreaCollapsed: Bool {\n        workspace?.utilityAreaModel?.isCollapsed ?? true\n    }\n\n    init(workspace: WorkspaceDocument) {\n        self.workspace = workspace\n        super.init(itemIdentifier: NSToolbarItem.Identifier(\"StartTaskToolbarItem\"))\n\n        image = NSImage(systemSymbolName: \"play.fill\", accessibilityDescription: nil)\n        let config = NSImage.SymbolConfiguration(pointSize: 14, weight: .regular)\n        image = image?.withSymbolConfiguration(config) ?? image\n\n        paletteLabel = \"Start Task\"\n        toolTip = \"Run the selected task\"\n        target = self\n        action = #selector(startTask)\n        isBordered = true\n    }\n\n    @objc\n    func startTask() {\n        guard let taskManager = workspace?.taskManager else { return }\n\n        taskManager.executeActiveTask()\n        if utilityAreaCollapsed {\n            CommandManager.shared.executeCommand(\"open.drawer\")\n        }\n        workspace?.utilityAreaModel?.selectedTab = .debugConsole\n        taskManager.taskShowingOutput = taskManager.selectedTaskID\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Tasks/ToolbarItems/StopTaskToolbarItem.swift",
    "content": "//\n//  StopTaskToolbarItem.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 8/28/25.\n//\n\nimport AppKit\nimport Combine\n\n@available(macOS 26, *)\nfinal class StopTaskToolbarItem: NSToolbarItem {\n    private weak var workspace: WorkspaceDocument?\n\n    private var taskManager: TaskManager? {\n        workspace?.taskManager\n    }\n\n    /// The listener that listens to the active task's status publisher. Is updated frequently as the active task\n    /// changes.\n    private var statusListener: AnyCancellable?\n    private var otherListeners: Set<AnyCancellable> = []\n\n    init?(workspace: WorkspaceDocument) {\n        guard let taskManager = workspace.taskManager else { return nil }\n\n        self.workspace = workspace\n        super.init(itemIdentifier: NSToolbarItem.Identifier(\"StopTaskToolbarItem\"))\n\n        image = NSImage(systemSymbolName: \"stop.fill\", accessibilityDescription: nil)\n        let config = NSImage.SymbolConfiguration(pointSize: 14, weight: .regular)\n        image = image?.withSymbolConfiguration(config) ?? image\n\n        paletteLabel = \"Stop Task\"\n        toolTip = \"Stop the selected task\"\n        target = self\n        isEnabled = false\n        isBordered = true\n\n        taskManager.$selectedTaskID.sink { [weak self] selectedId in\n            self?.updateStatusListener(activeTasks: taskManager.activeTasks, selectedId: selectedId)\n        }\n        .store(in: &otherListeners)\n\n        taskManager.$activeTasks.sink { [weak self] activeTasks in\n            self?.updateStatusListener(activeTasks: activeTasks, selectedId: taskManager.selectedTaskID)\n        }\n        .store(in: &otherListeners)\n\n        updateStatusListener(activeTasks: taskManager.activeTasks, selectedId: taskManager.selectedTaskID)\n    }\n\n    /// Update the ``statusListener`` to listen to a potentially new active task.\n    private func updateStatusListener(activeTasks: [UUID: CEActiveTask], selectedId: UUID?) {\n        statusListener?.cancel()\n\n        if let status = activeTasks[selectedId ?? UUID()]?.status {\n            updateForNewStatus(status)\n        }\n\n        guard let id = selectedId else { return }\n        statusListener = activeTasks[id]?.$status.sink { [weak self] status in\n            self?.updateForNewStatus(status)\n        }\n    }\n\n    private func updateForNewStatus(_ status: CETaskStatus) {\n        isEnabled = status == .running\n        action = isEnabled ? #selector(stopTask) : nil\n    }\n\n    @objc\n    func stopTask() {\n        taskManager?.terminateActiveTask()\n    }\n\n    deinit {\n        statusListener?.cancel()\n        otherListeners.removeAll()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Tasks/Views/StartTaskToolbarButton.swift",
    "content": "//\n//  StartTaskToolbarButton.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 8/4/24.\n//\n\nimport SwiftUI\n\nstruct StartTaskToolbarButton: View {\n    @Environment(\\.controlActiveState)\n    private var activeState\n\n    @ObservedObject var taskManager: TaskManager\n    @EnvironmentObject var workspace: WorkspaceDocument\n\n    var utilityAreaCollapsed: Bool {\n        workspace.utilityAreaModel?.isCollapsed ?? true\n    }\n\n    var body: some View {\n        Button {\n            taskManager.executeActiveTask()\n            if utilityAreaCollapsed {\n                CommandManager.shared.executeCommand(\"open.drawer\")\n            }\n            workspace.utilityAreaModel?.selectedTab = .debugConsole\n            taskManager.taskShowingOutput = taskManager.selectedTaskID\n        } label: {\n            Label(\"Start\", systemImage: \"play.fill\")\n                .labelStyle(.iconOnly)\n                .opacity(activeState == .inactive ? 0.5 : 1.0)\n                .font(.system(size: 18, weight: .regular))\n                .help(\"Start selected task\")\n                .frame(width: 28)\n                .offset(CGSize(width: 0, height: 2.5))\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Tasks/Views/StopTaskToolbarButton.swift",
    "content": "//\n//  StopTaskToolbarButton.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 8/3/24.\n//\n\nimport SwiftUI\nimport Combine\n\nstruct StopTaskToolbarButton: View {\n    @Environment(\\.controlActiveState)\n    private var activeState\n\n    @ObservedObject var taskManager: TaskManager\n\n    /// Tracks the current selected task's status. Updated by `updateStatusListener`\n    @State private var currentSelectedStatus: CETaskStatus?\n    /// The listener that listens to the active task's status publisher. Is updated frequently as the active task\n    /// changes.\n    @State private var statusListener: AnyCancellable?\n\n    var body: some View {\n        HStack {\n            if let currentSelectedStatus, currentSelectedStatus == .running {\n                    Button {\n                        taskManager.terminateActiveTask()\n                    } label: {\n                        Label(\"Stop\", systemImage: \"stop.fill\")\n                            .labelStyle(.iconOnly)\n                            .opacity(activeState == .inactive ? 0.5 : 1.0)\n                            .font(.system(size: 15, weight: .regular))\n                            .help(\"Stop selected task\")\n                            .frame(width: 28)\n                            .offset(y: 1.5)\n                    }\n                    .frame(height: 22)\n                    .transition(.opacity.combined(with: .move(edge: .trailing)))\n            }\n        }\n        .frame(width: 38, height: 22)\n        .animation(\n            .easeInOut(duration: 0.3),\n            value: currentSelectedStatus\n        )\n        .onChange(of: taskManager.selectedTaskID) { _, _ in updateStatusListener() }\n        .onChange(of: taskManager.activeTasks) { _, _ in updateStatusListener() }\n        .onAppear(perform: updateStatusListener)\n        .onDisappear {\n            statusListener?.cancel()\n        }\n    }\n\n    /// Update the ``statusListener`` to listen to a potentially new active task.\n    private func updateStatusListener() {\n        statusListener?.cancel()\n        currentSelectedStatus = taskManager.activeTasks[taskManager.selectedTaskID ?? UUID()]?.status\n        guard let id = taskManager.selectedTaskID else { return }\n        statusListener = taskManager.activeTasks[id]?.$status.sink { newValue in\n            currentSelectedStatus = newValue\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/TerminalEmulator/Model/CurrentUser.swift",
    "content": "//\n//  CurrentUser.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 6/2/24.\n//\n\nimport Foundation\n\n/// Represents the currently logged in user.\n///\n/// Do not initialize this struct, instead use ``CurrentUser/getCurrentUser()`` to create and fill in the information.\nstruct CurrentUser {\n    /// The user's username.\n    let name: String\n    /// The path to the user's shell executable.\n    let shell: String\n    /// The user's home directory path.\n    let homeDir: String\n    /// The users id.\n    let uid: uid_t\n    /// The user's group id.\n    let gid: gid_t\n\n    private init(name: String, shell: String, homeDir: String, uid: uid_t, gid: gid_t) {\n        self.name = name\n        self.shell = shell\n        self.homeDir = homeDir\n        self.uid = uid\n        self.gid = gid\n    }\n\n    /// Gets the current user using the `getpwuid_r` syscall.\n    static func getCurrentUser() -> CurrentUser? {\n        let bufsize = sysconf(_SC_GETPW_R_SIZE_MAX)\n        guard bufsize != -1 else { return nil }\n        let buffer = UnsafeMutablePointer<Int8>.allocate(capacity: bufsize)\n        defer {\n            buffer.deallocate()\n        }\n        var pwd = passwd()\n        // `result` will be set by getpwuid_r to point to `pwd` on success\n        var result: UnsafeMutablePointer<passwd>?\n\n        if getpwuid_r(getuid(), &pwd, buffer, bufsize, &result) != 0 { return nil }\n\n        return CurrentUser(\n            name: String(cString: pwd.pw_name),\n            shell: String(cString: pwd.pw_shell),\n            homeDir: String(cString: pwd.pw_dir),\n            uid: pwd.pw_uid,\n            gid: pwd.pw_gid\n        )\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/TerminalEmulator/Model/Shell.swift",
    "content": "//\n//  ShellIntegration.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 6/1/24.\n//\n\nimport Foundation\n\n/// Shells supported by CodeEdit\nenum Shell: String, CaseIterable {\n    case bash\n    case zsh\n\n    var url: String {\n        switch self {\n        case .bash:\n            \"/bin/bash\"\n        case .zsh:\n            \"/bin/zsh\"\n        }\n    }\n\n    var isSh: Bool {\n        switch self {\n        case .bash, .zsh:\n            return true\n        }\n    }\n\n    var defaultPath: String {\n        switch self {\n        case .bash:\n            \"/bin/bash\"\n        case .zsh:\n            \"/bin/zsh\"\n        }\n    }\n\n    /// Create the exec arguments for a new shell with the given behavior.\n    /// - Parameters:\n    ///   - interactive: The shell is interactive, accepts user input.\n    ///   - login: A login shell.\n    /// - Returns: The argument string.\n    func execArguments(interactive: Bool, login: Bool) -> String? {\n        var args = \"\"\n\n        switch self {\n        case .bash, .zsh:\n            if interactive {\n                args.append(\"i\")\n            }\n\n            if login {\n                args.append(\"l\")\n            }\n        }\n\n        return args.isEmpty ? nil : \"-\" + args\n    }\n\n    /// Gets the default shell from the current user and returns the string of the shell path.\n    ///\n    /// If getting the user's shell does not work, defaults to `zsh`,\n    static func autoDetectDefaultShell() -> String {\n        guard let currentUser = CurrentUser.getCurrentUser() else {\n            return Self.zsh.rawValue // macOS defaults to zsh\n        }\n        return currentUser.shell\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/TerminalEmulator/Model/ShellIntegration.swift",
    "content": "//\n//  ShellIntegration.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 6/2/24.\n//\n\nimport Foundation\nimport os\n\n/// Provides a single function for setting up shell integrations.\n/// See ``ShellIntegration/setUpIntegration(for:environment:)``\nenum ShellIntegration {\n    private static let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? \"\", category: \"ShellIntegration\")\n\n    /// Variable constants used by setup scripts.\n    enum Variables {\n        static let shellLogin = \"CE_SHELL_LOGIN\"\n        static let ceZDotDir = \"CE_ZDOTDIR\"\n        static let userZDotDir = \"USER_ZDOTDIR\"\n        static let zDotDir = \"ZDOTDIR\"\n        static let ceInjection = \"CE_INJECTION\"\n        static let disableHistory = \"CE_DISABLE_HISTORY\"\n    }\n\n    /// Errors for shell integration setup.\n    enum Error: Swift.Error, LocalizedError {\n        case bashShellFileNotFound\n        case zshShellFileNotFound\n\n        var localizedDescription: String {\n            switch self {\n            case .bashShellFileNotFound:\n                return \"Failed to find bash injection file.\"\n            case .zshShellFileNotFound:\n                return \"Failed to find zsh injection file.\"\n            }\n        }\n    }\n\n    /// Setup shell integration.\n    ///\n    /// Injects necessary init files for whatever shell is being used for CodeEdit to receive notifications about\n    /// running processes for display in the UI.\n    /// Any other setup/configuration should also be done here.\n    ///\n    /// - Parameters:\n    ///   - shell: The shell being set up.\n    ///   - environment: The existing environment variables. Passed as an `inout` parameter because this function will\n    ///                  modify this array.\n    ///   - useLogin: Whether or not to use a login shell.\n    /// - Returns: An array of args to pass to the shell executable.\n    /// - Throws: Errors involving filesystem operations. This function requires copying various files, which can\n    ///           throw. Can also throw ``ShellIntegration/Error`` errors if required files are not found in the bundle.\n    static func setUpIntegration(\n        for shell: Shell,\n        environment: inout [String],\n        useLogin: Bool,\n        interactive: Bool\n    ) throws -> [String] {\n        do {\n            logger.debug(\"Setting up shell: \\(shell.rawValue)\")\n            var args: [String] = []\n\n            // Enable injection in our scripts.\n            environment.append(\"\\(Variables.ceInjection)=1\")\n\n            switch shell {\n            case .bash:\n                try bash(&args)\n            case .zsh:\n                try zsh(&environment)\n            }\n\n            if useLogin {\n                environment.append(\"\\(Variables.shellLogin)=1\")\n            }\n\n            if let execArgs = shell.execArguments(interactive: interactive, login: useLogin) {\n                args.append(execArgs)\n            }\n\n            return args\n        } catch {\n            // catch so we can log this here\n            logger.error(\"Failed to setup shell integration: \\(error.localizedDescription)\")\n            throw error\n        }\n    }\n\n    // MARK: - Shell Specific Setup\n\n    /// Sets up the `bash` shell integration.\n    ///\n    /// Sets the bash `--init-file` option to point to CE's shell integration script. This script will source the\n    /// user's \"real\" init file and then install our required functions.\n    /// Also sets the `-i` option to initialize an interactive session if `interactive` is true.\n    ///\n    /// - Parameters:\n    ///   - args: The args to use for shell exec, will be modified by this function.\n    ///   - interactive: Set to true to use an interactive shell.\n    private static func bash(_ args: inout [String]) throws {\n        // Inject our own bash script that will execute the user's init files, then install our pre/post exec functions.\n        guard let scriptURL = Bundle.main.url(\n            forResource: \"codeedit_shell_integration\",\n            withExtension: \"bash\"\n        ) else {\n            throw Error.bashShellFileNotFound\n        }\n        args.append(contentsOf: [\"--init-file\", scriptURL.path()])\n    }\n\n    /// Sets up the `zsh` shell integration.\n    ///\n    /// Sets the zsh init directory to a temporary directory containing CE setup scripts. Each script corresponds to an\n    /// available zsh init script, and will source the user's real init script. To inject our `preexec/precmd` functions\n    /// we first source the user's zsh init files, then install our functions. Transparently installing our functions\n    /// and still using the user's init files w/o modifying anyone's rc files.\n    /// Also sets up an interactive session using the `-i` parameter.\n    ///\n    /// - Parameters:\n    ///   - shellExecArgs: The args to use for shell exec, will be modified by this function.\n    ///   - environment: Environment variables in an array. Formatted as `EnvVar=Value`. Will be modified by this\n    ///                  function.\n    ///   - useLogin: Whether to use a login shell.\n    ///   - interactive: Whether to use an interactive shell.\n    private static func zsh(\n        _ environment: inout [String]\n    ) throws {\n        // All injection script URLs\n        guard let profileScriptURL = Bundle.main.url(\n            forResource: \"codeedit_shell_integration_profile\",\n            withExtension: \"zsh\"\n        ), let envScriptURL = Bundle.main.url(\n            forResource: \"codeedit_shell_integration_env\",\n            withExtension: \"zsh\"\n        ), let loginScriptURL = Bundle.main.url(\n            forResource: \"codeedit_shell_integration_login\",\n            withExtension: \"zsh\"\n        ), let rcScriptURL = Bundle.main.url(\n            forResource: \"codeedit_shell_integration_rc\",\n            withExtension: \"zsh\"\n        ) else {\n            throw Error.zshShellFileNotFound\n        }\n\n        // Make the current user here to avoid a duplicate fetch.\n        let currentUser = CurrentUser.getCurrentUser()\n        let tempDir = try makeTempDir(forShell: .zsh, user: currentUser)\n\n        // Save any existing home dir. First getting a value from the environment.\n        // Falling back to the user's home dir, then ~\n        let envZDotDir = environment.first(where: { $0.starts(with: \"ZDOTDIR=\") })?.trimmingPrefix(\"ZDOTDIR=\")\n        let userZDotDir = (envZDotDir?.isEmpty ?? true) ? currentUser?.homeDir ?? \"~\" : String(envZDotDir ?? \"\")\n\n        environment.append(\"\\(Variables.zDotDir)=\\(tempDir.path())\")\n        environment.append(\"\\(Variables.userZDotDir)=\\(userZDotDir)\")\n\n        // Move all shell files to new temp dir\n        try copyFile(profileScriptURL, toDir: tempDir.appending(path: \".zprofile\"))\n        try copyFile(envScriptURL, toDir: tempDir.appending(path: \".zshenv\"))\n        try copyFile(loginScriptURL, toDir: tempDir.appending(path: \".zlogin\"))\n        try copyFile(rcScriptURL, toDir: tempDir.appending(path: \".zshrc\"))\n    }\n\n    /// Helper function for safely copying files, removing existing ones if needed.\n    /// - Parameters:\n    ///   - origin: The path of the file to copy from\n    ///   - destination: The destination URL to copy the file to.\n    /// - Throws: Errors from `FileManager` operations.\n    private static func copyFile(_ origin: URL, toDir destination: URL) throws {\n        if FileManager.default.fileExists(atPath: destination.path()) {\n            try FileManager.default.removeItem(at: destination)\n        }\n        try FileManager.default.copyItem(at: origin, to: destination)\n    }\n\n    /// Creates a temporary directory for a user/shell combination.\n    /// - Parameters:\n    ///   - shell: The shell to create the directory for.\n    ///   - user: The current user, will attempt to get the current user if none are supplied.\n    /// - Returns: The URL of the temporary directory.\n    /// - Throws: Errors from `FileManager` operations.\n    private static func makeTempDir(forShell shell: Shell, user: CurrentUser? = .getCurrentUser()) throws -> URL {\n        let username = user?.name ?? \"unknown\" // doesn't really matter but this is used later so might as well\n\n        // Create a temp directory to store our init files in.\n        // The name of the directory is user-specific and shell-specific to avoid overlap.\n        let tempDir = FileManager.default.temporaryDirectory.appending(\n            path: \"\\(username)-codeedit-\\(shell.rawValue)\"\n        )\n        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n        return tempDir\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/TerminalEmulator/Model/TerminalCache.swift",
    "content": "//\n//  TerminalCache.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/27/24.\n//\n\nimport Foundation\nimport SwiftTerm\n\n/// Stores a mapping of ID -> terminal view for reusing terminal views.\n/// This allows terminal views to continue to receive data even when not in the view hierarchy.\nfinal class TerminalCache {\n    static let shared: TerminalCache = TerminalCache()\n\n    /// The cache of terminal views.\n    private var terminals: [UUID: CELocalShellTerminalView]\n\n    private init() {\n        terminals = [:]\n    }\n\n    /// Get a cached terminal view.\n    /// - Parameter id: The ID of the terminal.\n    /// - Returns: The existing terminal, if it exists.\n    func getTerminalView(_ id: UUID) -> CELocalShellTerminalView? {\n        terminals[id]\n    }\n\n    /// Store a terminal view for reuse.\n    /// - Parameters:\n    ///   - id: The ID of the terminal.\n    ///   - view: The view representing the terminal's contents.\n    func cacheTerminalView(for id: UUID, view: CELocalShellTerminalView) {\n        terminals[id] = view\n    }\n\n    /// Remove any view associated with the terminal id.\n    /// - Parameter id: The ID of the terminal.\n    func removeCachedView(_ id: UUID) {\n        terminals[id] = nil\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/TerminalEmulator/Views/CEActiveTaskTerminalView.swift",
    "content": "//\n//  CEActiveTaskTerminalView.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/14/25.\n//\n\nimport AppKit\nimport SwiftTerm\n\nclass CEActiveTaskTerminalView: CELocalShellTerminalView {\n    var activeTask: CEActiveTask\n\n    var isUserCommandRunning: Bool {\n        activeTask.status == .running || activeTask.status == .stopped\n    }\n\n    init(activeTask: CEActiveTask) {\n        self.activeTask = activeTask\n        super.init(frame: .zero)\n    }\n\n    public required init?(coder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n    override func startProcess(\n        workspaceURL url: URL?,\n        shell: Shell? = nil,\n        environment: [String] = [],\n        interactive: Bool = true\n    ) {\n        let terminalSettings = Settings.shared.preferences.terminal\n\n        var terminalEnvironment: [String] = Terminal.getEnvironmentVariables()\n        terminalEnvironment.append(\"TERM_PROGRAM=CodeEditApp_Terminal\")\n\n        guard let (shell, shellPath) = getShell(shell, userSetting: terminalSettings.shell) else {\n            return\n        }\n        let shellArgs = [\"-lic\", activeTask.task.command]\n\n        terminalEnvironment.append(contentsOf: environment)\n        terminalEnvironment.append(\"\\(ShellIntegration.Variables.disableHistory)=1\")\n        terminalEnvironment.append(\n            contentsOf: activeTask.task.environmentVariables.map({ $0.key + \"=\" + $0.value })\n        )\n\n        sendOutputMessage(\"Starting task: \" + self.activeTask.task.name)\n        sendOutputMessage(self.activeTask.task.command)\n        newline()\n\n        process.startProcess(\n            executable: shellPath,\n            args: shellArgs,\n            environment: terminalEnvironment,\n            execName: shell.rawValue,\n            currentDirectory: URL(filePath: activeTask.task.workingDirectory, relativeTo: url).absolutePath\n        )\n    }\n\n    override func processTerminated(_ source: LocalProcess, exitCode: Int32?) {\n        activeTask.handleProcessFinished(terminationStatus: exitCode ?? 1)\n    }\n\n    func sendOutputMessage(_ message: String) {\n        sendSpecialSequence()\n        feed(text: message)\n        newline()\n    }\n\n    func sendSpecialSequence() {\n        let start: [UInt8] = [0x1B, 0x5B, 0x37, 0x6D]\n        let end: [UInt8] = [0x1B, 0x5B, 0x30, 0x6D]\n        feed(byteArray: start[0..<start.count])\n        feed(text: \" * \")\n        feed(byteArray: end[0..<end.count])\n        feed(text: \" \")\n    }\n\n    func newline() {\n        // cr cr lf\n        feed(byteArray: [13, 13, 10])\n    }\n\n    func runningPID() -> pid_t? {\n        if process.shellPid != 0 {\n            return process.shellPid\n        }\n        return nil\n    }\n\n    func getBufferAsString() -> String {\n        terminal.getText(\n            start: .init(col: 0, row: 0),\n            end: .init(col: terminal.cols, row: terminal.rows + terminal.buffer.yDisp)\n        )\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/TerminalEmulator/Views/CELocalShellTerminalView.swift",
    "content": "//\n//  CELocalShellTerminalView.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 8/7/24.\n//\n\nimport AppKit\nimport SwiftTerm\nimport Foundation\n\n/// # Dev Note (please read)\n///\n/// This entire file is a nearly 1:1 copy of SwiftTerm's `LocalProcessTerminalView`. The exception being the use of\n/// `CETerminalView` over `TerminalView`. This change was made to fix the terminal clearing when the view was given a\n/// frame of `0`. This enables terminals to keep running in the background, and allows them to be removed and added\n/// back into the hierarchy for use in the utility area.\n///\n/// # 07/15/25\n/// This has now been updated so that it differs from `LocalProcessTerminalView` in enough important ways that it\n/// should not be removed in the future even if SwiftTerm has a change in behavior.\n\nprotocol CELocalShellTerminalViewDelegate: AnyObject {\n    /// This method is invoked to notify that the terminal has been resized to the specified number of columns and rows\n    /// the user interface code might try to adjust the containing scroll view, or if it is a top level window, the\n    /// window itself\n    /// - Parameter source: the sending instance\n    /// - Parameter newCols: the new number of columns that should be shown\n    /// - Parameter newRow: the new number of rows that should be shown\n    func sizeChanged(source: CETerminalView, newCols: Int, newRows: Int)\n\n    /// This method is invoked when the title of the terminal window should be updated to the provided title\n    /// - Parameter source: the sending instance\n    /// - Parameter title: the desired title\n    func setTerminalTitle(source: CETerminalView, title: String)\n\n    /// Invoked when the OSC command 7 for \"current directory has changed\" command is sent\n    /// - Parameter source: the sending instance\n    /// - Parameter directory: the new working directory\n    func hostCurrentDirectoryUpdate(source: TerminalView, directory: String?)\n\n    /// This method will be invoked when the child process started by `startProcess` has terminated.\n    /// - Parameter source: the local process that terminated\n    /// - Parameter exitCode: the exit code returned by the process, or nil if this was an error caused during\n    ///                       the IO reading/writing\n    func processTerminated(source: TerminalView, exitCode: Int32?)\n}\n\n// MARK: - CELocalShellTerminalView\n\nclass CELocalShellTerminalView: CETerminalView, TerminalViewDelegate, LocalProcessDelegate {\n    var process: LocalProcess!\n\n    override public init(frame: CGRect) {\n        super.init(frame: frame)\n        setup()\n    }\n\n    public required init?(coder: NSCoder) {\n        super.init(coder: coder)\n        setup()\n    }\n\n    /// The `processDelegate` is used to deliver messages and information relevant to the execution of the terminal.\n    public weak var processDelegate: CELocalShellTerminalViewDelegate?\n\n    func setup() {\n        terminal = Terminal(delegate: self, options: TerminalOptions(scrollback: 2000))\n        terminalDelegate = self\n        process = LocalProcess(delegate: self)\n    }\n\n    /// Launches a child process inside a pseudo-terminal.\n    /// - Parameters:\n    ///     - workspaceURL: The URL of the workspace to start at.\n    ///     - shell: The shell to use, leave as `nil` to\n    public func startProcess(\n        workspaceURL url: URL?,\n        shell: Shell? = nil,\n        environment: [String] = [],\n        interactive: Bool = true\n    ) {\n        let terminalSettings = Settings.shared.preferences.terminal\n\n        var terminalEnvironment: [String] = Terminal.getEnvironmentVariables()\n        terminalEnvironment.append(\"TERM_PROGRAM=CodeEditApp_Terminal\")\n\n        guard let (shell, shellPath) = getShell(shell, userSetting: terminalSettings.shell) else {\n            return\n        }\n\n        processDelegate?.setTerminalTitle(source: self, title: shell.rawValue)\n\n        do {\n            let shellArgs: [String]\n            if terminalSettings.useShellIntegration {\n                shellArgs = try ShellIntegration.setUpIntegration(\n                    for: shell,\n                    environment: &terminalEnvironment,\n                    useLogin: terminalSettings.useLoginShell,\n                    interactive: interactive\n                )\n            } else {\n                shellArgs = []\n            }\n\n            terminalEnvironment.append(contentsOf: environment)\n\n            process.startProcess(\n                executable: shellPath,\n                args: shellArgs,\n                environment: terminalEnvironment,\n                execName: shell.rawValue,\n                currentDirectory: url?.absolutePath\n            )\n        } catch {\n            terminal.feed(text: \"Failed to start a terminal session: \\(error.localizedDescription)\")\n        }\n    }\n\n    /// Returns a string of a shell path to use\n    func getShell(_ shellType: Shell?, userSetting: SettingsData.TerminalShell) -> (Shell, String)? {\n        if let shellType {\n            return (shellType, shellType.defaultPath)\n        }\n        switch userSetting {\n        case .system:\n            let defaultShell = Shell.autoDetectDefaultShell()\n            guard let type = Shell(rawValue: NSString(string: defaultShell).lastPathComponent) else { return nil }\n            return (type, defaultShell)\n        case .bash:\n            return (.bash, \"/bin/bash\")\n        case .zsh:\n            return (.zsh, \"/bin/zsh\")\n        }\n    }\n\n    // MARK: - TerminalViewDelegate\n\n    /// This method is invoked to notify the client of the new columsn and rows that have been set by the UI\n    public func sizeChanged(source: TerminalView, newCols: Int, newRows: Int) {\n        guard process.running else {\n            return\n        }\n        var size = getWindowSize()\n        _ = PseudoTerminalHelpers.setWinSize(masterPtyDescriptor: process.childfd, windowSize: &size)\n\n        processDelegate?.sizeChanged(source: self, newCols: newCols, newRows: newRows)\n    }\n\n    public func clipboardCopy(source: TerminalView, content: Data) {\n        if let str = String(bytes: content, encoding: .utf8) {\n            let pasteBoard = NSPasteboard.general\n            pasteBoard.clearContents()\n            pasteBoard.writeObjects([str as NSString])\n        }\n    }\n\n    public func rangeChanged(source: TerminalView, startY: Int, endY: Int) { }\n\n    /// Invoke this method to notify the processDelegate of the new title for the terminal window\n    public func setTerminalTitle(source: TerminalView, title: String) {\n        processDelegate?.setTerminalTitle(source: self, title: title)\n    }\n\n    public func hostCurrentDirectoryUpdate(source: TerminalView, directory: String?) {\n        processDelegate?.hostCurrentDirectoryUpdate(source: source, directory: directory)\n    }\n\n    /// Passes data from the terminal to the shell.\n    /// Eg, the user types characters, this forwards the data to the shell.\n    public func send(source: TerminalView, data: ArraySlice<UInt8>) {\n        process.send(data: data)\n    }\n\n    public func scrolled(source: TerminalView, position: Double) { }\n\n    // MARK: - LocalProcessDelegate\n\n    /// Implements the LocalProcessDelegate method.\n    public func processTerminated(_ source: LocalProcess, exitCode: Int32?) {\n        processDelegate?.processTerminated(source: self, exitCode: exitCode)\n    }\n\n    /// Implements the LocalProcessDelegate.dataReceived method\n    ///\n    /// Passes data from the shell to the terminal.\n    public func dataReceived(slice: ArraySlice<UInt8>) {\n        feed(byteArray: slice)\n    }\n\n    /// Implements the LocalProcessDelegate.getWindowSize method\n    public func getWindowSize() -> winsize {\n        let frame: CGRect = self.frame\n        return winsize(\n            ws_row: UInt16(getTerminal().rows),\n            ws_col: UInt16(getTerminal().cols),\n            ws_xpixel: UInt16(frame.width),\n            ws_ypixel: UInt16(frame.height)\n        )\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/TerminalEmulator/Views/CETerminalView.swift",
    "content": "//\n//  CETerminalView.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/11/25.\n//\n\nimport SwiftTerm\nimport AppKit\n\n/// # Please see dev note in ``CELocalShellTerminalView``!\n\nclass CETerminalView: TerminalView {\n    override func setFrameSize(_ newSize: NSSize) {\n        if newSize != .zero {\n            super.setFrameSize(newSize)\n        }\n    }\n\n    override open var frame: CGRect {\n        get {\n            super.frame\n        }\n        set {\n            if newValue.size != .zero {\n                super.frame = newValue\n            }\n        }\n    }\n\n    @objc\n    override open func copy(_ sender: Any) {\n        let range = selectedPositions()\n        let text = terminal.getText(start: range.start, end: range.end)\n        let pasteboard = NSPasteboard.general\n        pasteboard.clearContents()\n        pasteboard.setString(text, forType: .string)\n    }\n\n    override open func isAccessibilityElement() -> Bool {\n        true\n    }\n\n    override open func isAccessibilityEnabled() -> Bool {\n        true\n    }\n\n    override open func accessibilityLabel() -> String? {\n        \"Terminal Emulator\"\n    }\n\n    override open func accessibilityRole() -> NSAccessibility.Role? {\n        .textArea\n    }\n\n    override open func accessibilityValue() -> Any? {\n        terminal.getText(\n            start: Position(col: 0, row: 0),\n            end: Position(col: terminal.buffer.x, row: terminal.getTopVisibleRow() + terminal.rows)\n        )\n    }\n\n    override open func accessibilitySelectedText() -> String? {\n        let range = selectedPositions()\n        let text = terminal.getText(start: range.start, end: range.end)\n        return text\n    }\n\n}\n"
  },
  {
    "path": "CodeEdit/Features/TerminalEmulator/Views/TerminalEmulatorView+Coordinator.swift",
    "content": "//\n//  TerminalEmulatorView+Coordinator.swift\n//  CodeEditModules/TerminalEmulator\n//\n//  Created by Lukas Pistrol on 24.03.22.\n//\n\nimport SwiftUI\nimport SwiftTerm\n\nextension TerminalEmulatorView {\n    final class Coordinator: NSObject, CELocalShellTerminalViewDelegate {\n        private let terminalID: UUID\n        public var onTitleChange: (_ title: String) -> Void\n\n        var mode: TerminalMode\n\n        init(terminalID: UUID, mode: TerminalMode, onTitleChange: @escaping (_ title: String) -> Void) {\n            self.terminalID = terminalID\n            self.onTitleChange = onTitleChange\n            self.mode = mode\n            super.init()\n        }\n\n        func hostCurrentDirectoryUpdate(source: TerminalView, directory: String?) {}\n\n        func sizeChanged(source: CETerminalView, newCols: Int, newRows: Int) {}\n\n        func setTerminalTitle(source: CETerminalView, title: String) {\n            onTitleChange(title)\n        }\n\n        func processTerminated(source: TerminalView, exitCode: Int32?) {\n            guard let exitCode else {\n                return\n            }\n            if case .shell = mode {\n                source.feed(text: \"Exit code: \\(exitCode)\\n\\r\\n\")\n                source.feed(text: \"To open a new session, create a new terminal tab.\")\n                TerminalCache.shared.removeCachedView(terminalID)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/TerminalEmulator/Views/TerminalEmulatorView.swift",
    "content": "//\n//  TerminalEmulatorView.swift\n//  CodeEditModules/TerminalEmulator\n//\n//  Created by Lukas Pistrol on 22.03.22.\n//\n\nimport SwiftUI\nimport SwiftTerm\n\n/// # TerminalEmulatorView\n///\n/// A terminal emulator view.\n///\n/// Wraps a `LocalProcessTerminalView` from `SwiftTerm` inside a `NSViewRepresentable`\n/// for use in SwiftUI.\n///\n/// Caches the view in the ``TerminalCache`` to keep terminal state when the view is removed from the hierarchy.\n///\nstruct TerminalEmulatorView: NSViewRepresentable {\n    enum TerminalMode {\n        case shell(shellType: Shell?)\n        case task(activeTask: CEActiveTask)\n    }\n\n    @AppSettings(\\.terminal)\n    var terminalSettings\n    @AppSettings(\\.textEditing.font)\n    var fontSettings\n\n    @StateObject private var themeModel: ThemeModel = .shared\n\n    private var font: NSFont {\n        if terminalSettings.useTextEditorFont {\n            return fontSettings.current\n        } else {\n            return terminalSettings.font.current\n        }\n    }\n\n    private let terminalID: UUID\n    private var url: URL\n\n    public var mode: TerminalMode\n    public var onTitleChange: (_ title: String) -> Void\n\n    /// Create an emulator view\n    /// - Parameters:\n    ///   - url: The URL the emulator should start at.\n    ///   - terminalID: The ID of the terminal. Used to restore state when switching away from the view.\n    ///   - shellType: The type of shell to use. Overrides any settings or auto-detection.\n    ///   - onTitleChange: A callback used when the terminal updates it's title.\n    init(url: URL, terminalID: UUID, shellType: Shell? = nil, onTitleChange: @escaping (_ title: String) -> Void) {\n        self.url = url\n        self.terminalID = terminalID\n        self.mode = .shell(shellType: shellType)\n        self.onTitleChange = onTitleChange\n    }\n\n    init(url: URL, task: CEActiveTask) {\n        terminalID = task.task.id\n        self.url = url\n        self.mode = .task(activeTask: task)\n        self.onTitleChange = { _ in }\n    }\n\n    // MARK: - Settings\n\n    private func getTerminalCursor() -> CursorStyle {\n        let blink = terminalSettings.cursorBlink\n        switch terminalSettings.cursorStyle {\n        case .block:\n            return blink ? .blinkBlock : .steadyBlock\n        case .underline:\n            return blink ? .blinkUnderline : .steadyUnderline\n        case .bar:\n            return blink ? .blinkBar : .steadyBar\n        }\n    }\n\n    /// Returns true if the `option` key should be treated as the `meta` key.\n    private var optionAsMeta: Bool {\n        terminalSettings.optionAsMeta\n    }\n\n    /// Returns the mapped array of `SwiftTerm.Color` objects of ANSI Colors\n    private var colors: [SwiftTerm.Color] {\n        if let selectedTheme = Settings[\\.theme].matchAppearance && Settings[\\.terminal].darkAppearance\n            ? themeModel.selectedDarkTheme\n            : themeModel.selectedTheme,\n           let index = themeModel.themes.firstIndex(of: selectedTheme) {\n            return themeModel.themes[index].terminal.ansiColors.map { color in\n                SwiftTerm.Color(hex: color)\n            }\n        }\n        return []\n    }\n\n    /// Returns the `cursor` color of the selected theme\n    private var cursorColor: NSColor {\n        if let selectedTheme = Settings[\\.theme].matchAppearance && Settings[\\.terminal].darkAppearance\n            ? themeModel.selectedDarkTheme\n            : themeModel.selectedTheme,\n           let index = themeModel.themes.firstIndex(of: selectedTheme) {\n            return NSColor(themeModel.themes[index].terminal.cursor.swiftColor)\n        }\n        return NSColor(.accentColor)\n    }\n\n    /// Returns the `selection` color of the selected theme\n    private var selectionColor: NSColor {\n        if let selectedTheme = Settings[\\.theme].matchAppearance && Settings[\\.terminal].darkAppearance\n            ? themeModel.selectedDarkTheme\n            : themeModel.selectedTheme,\n           let index = themeModel.themes.firstIndex(of: selectedTheme) {\n            return NSColor(themeModel.themes[index].terminal.selection.swiftColor)\n        }\n        return NSColor(.accentColor)\n    }\n\n    /// Returns the `text` color of the selected theme\n    private var textColor: NSColor {\n        if let selectedTheme = Settings[\\.theme].matchAppearance && Settings[\\.terminal].darkAppearance\n            ? themeModel.selectedDarkTheme\n            : themeModel.selectedTheme,\n           let index = themeModel.themes.firstIndex(of: selectedTheme) {\n            return NSColor(themeModel.themes[index].terminal.text.swiftColor)\n        }\n        return NSColor(.primary)\n    }\n\n    /// Returns the `background` color of the selected theme\n    private var backgroundColor: NSColor {\n        return .clear\n    }\n\n    /// returns a `NSAppearance` based on the user setting of the terminal appearance,\n    /// `nil` if app default is not overridden\n    private var colorAppearance: NSAppearance? {\n        if terminalSettings.darkAppearance {\n            return .init(named: .darkAqua)\n        }\n        return nil\n    }\n\n    // MARK: - NSViewRepresentable\n\n    /// Inherited from NSViewRepresentable.makeNSView(context:).\n    func makeNSView(context: Context) -> CELocalShellTerminalView {\n        let view: CELocalShellTerminalView\n\n        switch mode {\n        case .shell(let shellType):\n            let isCached = TerminalCache.shared.getTerminalView(terminalID) != nil\n            view = TerminalCache.shared.getTerminalView(terminalID) ?? CELocalShellTerminalView(frame: .zero)\n            if !isCached {\n                view.startProcess(workspaceURL: url, shell: shellType)\n                configureView(view)\n            }\n        case .task(let activeTask):\n            if let output = activeTask.output {\n                view = output\n            } else {\n                let newView = CEActiveTaskTerminalView(activeTask: activeTask)\n                activeTask.output = newView\n                view = newView\n            }\n            if !activeTask.hasOutputBeenConfigured {\n                configureView(view)\n                activeTask.hasOutputBeenConfigured = true\n            }\n        }\n\n        view.processDelegate = context.coordinator\n\n        TerminalCache.shared.cacheTerminalView(for: terminalID, view: view)\n        return view\n    }\n\n    func configureView(_ terminal: CELocalShellTerminalView) {\n        terminal.getTerminal().silentLog = true\n        terminal.appearance = colorAppearance\n        scroller(terminal)?.isHidden = true\n        terminal.font = font\n        terminal.installColors(self.colors)\n        terminal.caretColor = cursorColor.withAlphaComponent(0.5)\n        terminal.caretTextColor = cursorColor.withAlphaComponent(0.5)\n        terminal.selectedTextBackgroundColor = selectionColor\n        terminal.nativeForegroundColor = textColor\n        terminal.nativeBackgroundColor = terminalSettings.useThemeBackground ? backgroundColor : .clear\n        terminal.cursorStyleChanged(source: terminal.getTerminal(), newStyle: getTerminalCursor())\n        terminal.layer?.backgroundColor = CGColor.clear\n        terminal.optionAsMetaKey = optionAsMeta\n    }\n\n    private func scroller(_ terminal: CELocalShellTerminalView) -> NSScroller? {\n        for subView in terminal.subviews {\n            if let scroller = subView as? NSScroller {\n                return scroller\n            }\n        }\n        return nil\n    }\n\n    func updateNSView(_ view: CELocalShellTerminalView, context: Context) {\n        view.installColors(self.colors)\n        view.caretColor = cursorColor.withAlphaComponent(0.5)\n        view.caretTextColor = cursorColor.withAlphaComponent(0.5)\n        view.selectedTextBackgroundColor = selectionColor\n        view.nativeForegroundColor = textColor\n        view.nativeBackgroundColor = terminalSettings.useThemeBackground ? backgroundColor : .clear\n        view.layer?.backgroundColor = .clear\n        view.optionAsMetaKey = optionAsMeta\n        view.cursorStyleChanged(source: view.getTerminal(), newStyle: getTerminalCursor())\n        view.appearance = colorAppearance\n        view.getTerminal().softReset()\n        view.feed(text: \"\") // send empty character to force colors to be redrawn\n    }\n\n    func makeCoordinator() -> Coordinator {\n        Coordinator(terminalID: terminalID, mode: mode, onTitleChange: onTitleChange)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/DebugUtility/TaskOutputActionsView.swift",
    "content": "//\n//  TaskOutputActionsView.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 27.06.24.\n//\n\nimport SwiftUI\n\nstruct TaskOutputActionsView: View {\n    @ObservedObject var activeTask: CEActiveTask\n    @ObservedObject var taskManager: TaskManager\n    @Binding var scrollProxy: ScrollViewProxy?\n\n    @Namespace var bottomID\n    var body: some View {\n        HStack {\n            Spacer()\n\n            Button {\n                Task {\n                    await taskManager.runTask(task: activeTask.task)\n                }\n            } label: {\n                Image(systemName: \"memories\")\n                    .foregroundStyle(.green)\n            }\n            .buttonStyle(.icon)\n            .help(\"Run Task\")\n\n            Button {\n                taskManager.terminateTask(taskID: activeTask.task.id)\n            } label: {\n                Image(systemName: \"stop.fill\")\n                    .foregroundStyle(\n                        (activeTask.status == .running || activeTask.status == .stopped) ? .red : .gray\n                    )\n            }\n            .buttonStyle(.icon)\n            .disabled(!(activeTask.status == .running || activeTask.status == .stopped))\n            .help(\"Stop Task\")\n\n            Button {\n                if activeTask.status == .stopped {\n                    activeTask.resume()\n                } else if activeTask.status == .running {\n                    activeTask.suspend()\n                }\n            } label: {\n                if activeTask.status == .stopped {\n                    Image(systemName: \"play\")\n                } else {\n                    Image(systemName: \"pause\")\n                }\n            }\n            .buttonStyle(.icon)\n            .disabled(!(activeTask.status == .running || activeTask.status == .stopped))\n            .opacity(activeTask.status == .running || activeTask.status == .stopped ? 1 : 0.5)\n            .help(activeTask.status == .stopped ? \"Resume Task\" : \"Suspend Task\")\n\n            Divider()\n\n            Button {\n                withAnimation {\n                    scrollProxy?.scrollTo(bottomID, anchor: .bottom)\n                }\n            } label: {\n                Image(systemName: \"text.append\")\n            }\n            .buttonStyle(.icon)\n            .help(\"Scroll down to the bottom\")\n\n            Button {\n                activeTask.clearOutput()\n            } label: {\n                Image(systemName: \"trash\")\n            }\n            .buttonStyle(.icon)\n            .help(\"Clear Output\")\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/DebugUtility/TaskOutputView.swift",
    "content": "//\n//  TaskOutputView.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 27.06.24.\n//\n\nimport SwiftUI\n\nstruct TaskOutputView: View {\n    @ObservedObject var activeTask: CEActiveTask\n\n    var body: some View {\n        if activeTask.output != nil, let workspaceURL = activeTask.workspaceURL {\n            TerminalEmulatorView(url: workspaceURL, task: activeTask)\n        } else {\n            EmptyView()\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/DebugUtility/UtilityAreaDebugView.swift",
    "content": "//\n//  UtilityAreaDebugView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 5/25/23.\n//\n\nimport SwiftUI\n\nstruct UtilityAreaDebugView: View {\n    @AppSettings(\\.theme.matchAppearance)\n    private var matchAppearance\n    @AppSettings(\\.terminal.darkAppearance)\n    private var darkAppearance\n    @AppSettings(\\.theme.useThemeBackground)\n    private var useThemeBackground\n\n    @AppSettings(\\.textEditing.font)\n    private var textEditingFont\n    @AppSettings(\\.terminal.font)\n    private var terminalFont\n    @AppSettings(\\.terminal.useTextEditorFont)\n    private var useTextEditorFont\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @EnvironmentObject private var utilityAreaViewModel: UtilityAreaViewModel\n    @EnvironmentObject private var taskManager: TaskManager\n\n    @State private var scrollProxy: ScrollViewProxy?\n\n    @StateObject private var themeModel: ThemeModel = .shared\n\n    @Namespace var bottomID\n\n    var font: NSFont {\n        useTextEditorFont == true ? textEditingFont.current : terminalFont.current\n    }\n\n    var body: some View {\n        UtilityAreaTabView(model: utilityAreaViewModel.tabViewModel) { _ in\n            ZStack {\n                HStack { Spacer() }\n\n                if let taskShowingOutput = taskManager.taskShowingOutput,\n                   let activeTask = taskManager.activeTasks[taskShowingOutput] {\n                    GeometryReader { geometry in\n                        let containerHeight = geometry.size.height\n                        let totalFontHeight = fontTotalHeight(nsFont: font).rounded(.up)\n                        let constrainedHeight = containerHeight - containerHeight.truncatingRemainder(\n                            dividingBy: totalFontHeight\n                        )\n                        VStack(spacing: 0) {\n                            Spacer(minLength: 0).frame(minHeight: 0)\n\n                            TaskOutputView(activeTask: activeTask)\n                                .frame(height: max(0, constrainedHeight - 1))\n                                .id(activeTask.task.id)\n                                .padding(.horizontal, 10)\n                        }\n                    }\n                    .paneToolbar {\n                        TaskOutputActionsView(\n                            activeTask: activeTask,\n                            taskManager: taskManager,\n                            scrollProxy: $scrollProxy,\n                            bottomID: _bottomID\n                        )\n                    }\n                    .background {\n                        if utilityAreaViewModel.selectedTerminals.isEmpty {\n                            EffectView(.contentBackground)\n                        } else if useThemeBackground {\n                            Color(nsColor: backgroundColor)\n                        } else {\n                            if colorScheme == .dark {\n                                EffectView(.underPageBackground)\n                            } else {\n                                EffectView(.contentBackground)\n                            }\n                        }\n                    }\n                    .colorScheme(\n                        utilityAreaViewModel.selectedTerminals.isEmpty\n                        ? colorScheme\n                        : matchAppearance && darkAppearance\n                        ? themeModel.selectedDarkTheme?.appearance == .dark ? .dark : .light\n                        : themeModel.selectedTheme?.appearance == .dark ? .dark : .light\n                    )\n                } else {\n                    CEContentUnavailableView(\"No Task Selected\")\n                }\n            }\n        } leadingSidebar: { _ in\n            ZStack {\n                Text(\"No Tasks are Running\")\n                    .font(.system(size: 16))\n                    .foregroundColor(.secondary)\n                    .frame(maxWidth: .infinity, maxHeight: .infinity)\n                    .opacity(taskManager.activeTasks.isEmpty ? 1 : 0)\n\n                List(selection: $taskManager.taskShowingOutput) {\n                    ForEach(Array(taskManager.activeTasks.keys), id: \\.self) { taskID in\n                        if let activeTask = taskManager.activeTasks[taskID] {\n                            ActiveTaskView(activeTask: activeTask)\n                                .onTapGesture {\n                                    taskManager.taskShowingOutput = taskID\n                                }\n                                .contextMenu(\n                                    ContextMenu {\n                                        Button {\n                                            taskManager.deleteTask(taskID: taskID)\n                                        } label: {\n                                            Text(\"Delete\")\n                                        }\n                                    }\n                                )\n                        }\n                    }\n                }\n                .listStyle(.automatic)\n                .accentColor(.secondary)\n                .paneToolbar { Spacer() } // Background\n            }\n        }.onReceive(taskManager.$activeTasks) { newTasks in\n            if taskManager.taskShowingOutput == nil {\n                taskManager.taskShowingOutput = newTasks.first?.key\n            }\n        }\n    }\n\n    /// Returns the `background` color of the selected theme\n    private var backgroundColor: NSColor {\n        if let selectedTheme = matchAppearance && darkAppearance\n            ? themeModel.selectedDarkTheme\n            : themeModel.selectedTheme,\n           let index = themeModel.themes.firstIndex(of: selectedTheme) {\n            return NSColor(themeModel.themes[index].terminal.background.swiftColor)\n        }\n        return .windowBackgroundColor\n    }\n\n    /// Estimate the font's height for keeping the terminal aligned with the bottom.\n    /// - Parameter nsFont: The font being used in the terminal.\n    /// - Returns: The height in pixels of the font.\n    private func fontTotalHeight(nsFont: NSFont) -> CGFloat {\n        let ctFont = nsFont as CTFont\n        let ascent = CTFontGetAscent(ctFont)\n        let descent = CTFontGetDescent(ctFont)\n        let leading = CTFontGetLeading(ctFont)\n        return ascent + descent + leading\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/Models/UtilityAreaTab.swift",
    "content": "//\n//  UtilityAreaTab.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 02/06/2023.\n//\n\nimport SwiftUI\n\nenum UtilityAreaTab: WorkspacePanelTab, CaseIterable {\n    var id: Self { self }\n\n    case terminal\n    case debugConsole\n    case output\n\n    var title: String {\n        switch self {\n        case .terminal:\n            return \"Terminal\"\n        case .debugConsole:\n            return \"Debug Console\"\n        case .output:\n            return \"Output\"\n        }\n    }\n\n    var systemImage: String {\n        switch self {\n        case .terminal:\n            return \"terminal\"\n        case .debugConsole:\n            return \"ladybug\"\n        case .output:\n            return \"list.bullet.indent\"\n        }\n    }\n\n    var body: some View {\n        switch self {\n        case .terminal:\n            UtilityAreaTerminalView()\n        case .debugConsole:\n            UtilityAreaDebugView()\n        case .output:\n            UtilityAreaOutputView()\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/Models/UtilityAreaTerminal.swift",
    "content": "//\n//  UtilityAreaTerminal.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/27/24.\n//\n\nimport Foundation\n\nfinal class UtilityAreaTerminal: ObservableObject, Identifiable, Equatable {\n    let id: UUID\n    @Published var url: URL\n    @Published var title: String\n    @Published var terminalTitle: String\n    @Published var shell: Shell?\n    @Published var customTitle: Bool\n\n    init(id: UUID, url: URL, title: String, shell: Shell?) {\n        self.id = id\n        self.title = title\n        self.terminalTitle = title\n        self.url = url\n        self.shell = shell\n        self.customTitle = false\n    }\n\n    static func == (lhs: UtilityAreaTerminal, rhs: UtilityAreaTerminal) -> Bool {\n        lhs.id == rhs.id\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/OutputUtility/Model/Sources/ExtensionUtilityAreaOutputSource.swift",
    "content": "//\n//  ExtensionUtilityAreaOutputSource.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/18/25.\n//\n\nimport OSLog\nimport LogStream\n\nextension LogMessage: @retroactive Identifiable, UtilityAreaOutputMessage {\n    public var id: String {\n        \"\\(date.timeIntervalSince1970)\" + process + (subsystem ?? \"\") + (category ?? \"\")\n    }\n\n    var level: UtilityAreaLogLevel {\n        switch type {\n        case .fault, .error:\n                .error\n        case .info, .default:\n                .info\n        case .debug:\n                .debug\n        default:\n                .info\n        }\n    }\n}\n\nstruct ExtensionUtilityAreaOutputSource: UtilityAreaOutputSource {\n    var id: String {\n        \"extension_output\" + extensionInfo.id\n    }\n\n    let extensionInfo: ExtensionInfo\n\n    func cachedMessages() -> [LogMessage] {\n        []\n    }\n\n    func streamMessages() -> AsyncStream<LogMessage> {\n        LogStream.logs(for: extensionInfo.pid, flags: [.info, .historical, .processOnly])\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/OutputUtility/Model/Sources/InternalDevelopmentOutputSource.swift",
    "content": "//\n//  InternalDevelopmentOutputSource.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/18/25.\n//\n\nimport Foundation\n\nclass InternalDevelopmentOutputSource: UtilityAreaOutputSource {\n    static let shared = InternalDevelopmentOutputSource()\n\n    struct Message: UtilityAreaOutputMessage {\n        var id: UUID = UUID()\n\n        var message: String\n        var date: Date = Date()\n        var subsystem: String?\n        var category: String?\n        var level: UtilityAreaLogLevel\n    }\n\n    var id: UUID = UUID()\n    private var logs: [Message] = []\n    private(set) var streamContinuation: AsyncStream<Message>.Continuation\n    private var stream: AsyncStream<Message>\n\n    init() {\n        (stream, streamContinuation) = AsyncStream<Message>.makeStream()\n    }\n\n    func pushLog(_ log: Message) {\n        logs.append(log)\n        streamContinuation.yield(log)\n    }\n\n    func cachedMessages() -> [Message] {\n        logs\n    }\n\n    func streamMessages() -> AsyncStream<Message> {\n        stream\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/OutputUtility/Model/Sources/LanguageServerLogContainer.swift",
    "content": "//\n//  LanguageServerLogContainer.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/18/25.\n//\n\nimport OSLog\nimport LanguageServerProtocol\n\nclass LanguageServerLogContainer: UtilityAreaOutputSource {\n    struct LanguageServerMessage: UtilityAreaOutputMessage {\n        let log: LogMessageParams\n        var id: UUID = UUID()\n\n        var message: String {\n            log.message\n        }\n\n        var level: UtilityAreaLogLevel {\n            switch log.type {\n            case .error:\n                    .error\n            case .warning:\n                    .warning\n            case .info:\n                    .info\n            case .log:\n                    .debug\n            }\n        }\n\n        var date: Date = Date()\n        var subsystem: String?\n        var category: String?\n    }\n\n    let id: String\n\n    private var streamContinuation: AsyncStream<LanguageServerMessage>.Continuation\n    private var stream: AsyncStream<LanguageServerMessage>\n    private(set) var logs: [LanguageServerMessage] = []\n\n    init(language: LanguageIdentifier) {\n        id = language.rawValue\n        (stream, streamContinuation) = AsyncStream<LanguageServerMessage>.makeStream(\n            bufferingPolicy: .bufferingNewest(0)\n        )\n    }\n\n    func appendLog(_ log: LogMessageParams) {\n        let message = LanguageServerMessage(log: log)\n        logs.append(message)\n        streamContinuation.yield(message)\n    }\n\n    func cachedMessages() -> [LanguageServerMessage] {\n        logs\n    }\n\n    func streamMessages() -> AsyncStream<LanguageServerMessage> {\n        stream\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/OutputUtility/Model/UtilityAreaLogLevel.swift",
    "content": "//\n//  UtilityAreaLogLevel.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/18/25.\n//\n\nimport SwiftUI\n\nenum UtilityAreaLogLevel {\n    case error\n    case warning\n    case info\n    case debug\n\n    var iconName: String {\n        switch self {\n        case .error:\n            \"exclamationmark.3\"\n        case .warning:\n            \"exclamationmark.2\"\n        case .info:\n            \"info\"\n        case .debug:\n            \"stethoscope\"\n        }\n    }\n\n    var color: Color {\n        switch self {\n        case .error:\n            return Color(red: 202.0/255.0, green: 27.0/255.0, blue: 0)\n        case .warning:\n            return Color(red: 255.0/255.0, green: 186.0/255.0, blue: 0)\n        case .info:\n            return .cyan\n        case .debug:\n            return .coolGray\n        }\n    }\n\n    var backgroundColor: Color {\n        switch self {\n        case .error:\n            color.opacity(0.1)\n        case .warning:\n            color.opacity(0.2)\n        case .info, .debug:\n                .clear\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/OutputUtility/Model/UtilityAreaOutputSource.swift",
    "content": "//\n//  UtilityAreaOutputSource.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/18/25.\n//\n\nimport OSLog\nimport LogStream\n\nprotocol UtilityAreaOutputMessage: Identifiable {\n    var message: String { get }\n    var date: Date { get }\n    var subsystem: String? { get }\n    var category: String? { get }\n    var level: UtilityAreaLogLevel { get }\n}\n\nprotocol UtilityAreaOutputSource: Identifiable {\n    associatedtype Message: UtilityAreaOutputMessage\n    func cachedMessages() -> [Message]\n    func streamMessages() -> AsyncStream<Message>\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/OutputUtility/View/UtilityAreaOutputLogList.swift",
    "content": "//\n//  UtilityAreaOutputLogList.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/18/25.\n//\n\nimport SwiftUI\n\nstruct UtilityAreaOutputLogList<Source: UtilityAreaOutputSource, Toolbar: View>: View {\n    let source: Source\n\n    @State var output: [Source.Message] = []\n    @Binding var filterText: String\n    var toolbar: () -> Toolbar\n\n    init(source: Source, filterText: Binding<String>, @ViewBuilder toolbar: @escaping () -> Toolbar) {\n        self.source = source\n        self._filterText = filterText\n        self.toolbar = toolbar\n    }\n\n    var filteredOutput: [Source.Message] {\n        if filterText.isEmpty {\n            return output\n        }\n        return output.filter { item in\n            return filterText == \"\" ? true : item.message.contains(filterText)\n        }\n    }\n\n    var body: some View {\n        List(filteredOutput.reversed()) { item in\n            VStack(spacing: 2) {\n                HStack(spacing: 0) {\n                    Text(item.message)\n                        .fontDesign(.monospaced)\n                        .font(.system(size: 12, weight: .regular).monospaced())\n                    Spacer(minLength: 0)\n                }\n                HStack(spacing: 6) {\n                    HStack(spacing: 4) {\n                        Image(systemName: item.level.iconName)\n                            .foregroundColor(.white)\n                            .font(.system(size: 7, weight: .semibold))\n                            .frame(width: 12, height: 12)\n                            .background(\n                                RoundedRectangle(cornerRadius: 2)\n                                    .fill(item.level.color)\n                                    .aspectRatio(1.0, contentMode: .fit)\n                            )\n                        Text(item.date.logFormatted())\n                            .fontWeight(.medium)\n                    }\n                    if let subsystem = item.subsystem {\n                        HStack(spacing: 2) {\n                            Image(systemName: \"gearshape.2\")\n                                .font(.system(size: 8, weight: .regular))\n                            Text(subsystem)\n                        }\n                    }\n                    if let category = item.category {\n                        HStack(spacing: 2) {\n                            Image(systemName: \"square.grid.3x3\")\n                                .font(.system(size: 8, weight: .regular))\n                            Text(category)\n                        }\n                    }\n                    Spacer(minLength: 0)\n                }\n                .foregroundStyle(.secondary)\n                .font(.system(size: 9, weight: .semibold).monospaced())\n            }\n            .rotationEffect(.radians(.pi))\n            .scaleEffect(x: -1, y: 1, anchor: .center)\n            .alignmentGuide(.listRowSeparatorLeading) { _ in 0 }\n            .listRowBackground(item.level.backgroundColor)\n        }\n        .listStyle(.plain)\n        .listRowInsets(EdgeInsets())\n        .rotationEffect(.radians(.pi))\n        .scaleEffect(x: -1, y: 1, anchor: .center)\n        .task(id: source.id) {\n            output = source.cachedMessages()\n            for await item in source.streamMessages() {\n                output.append(item)\n            }\n        }\n        .paneToolbar {\n            toolbar()\n            Spacer()\n            UtilityAreaFilterTextField(title: \"Filter\", text: $filterText)\n                .frame(maxWidth: 175)\n            Button {\n                output.removeAll(keepingCapacity: true)\n            } label: {\n                Image(systemName: \"trash\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/OutputUtility/View/UtilityAreaOutputSourcePicker.swift",
    "content": "//\n//  UtilityAreaOutputSourcePicker.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/18/25.\n//\n\nimport SwiftUI\n\nstruct UtilityAreaOutputSourcePicker: View {\n    typealias Sources = UtilityAreaOutputView.Sources\n\n    @EnvironmentObject private var workspace: WorkspaceDocument\n\n    @AppSettings(\\.developerSettings.showInternalDevelopmentInspector)\n    var showInternalDevelopmentInspector\n\n    @Binding var selectedSource: Sources?\n\n    @ObservedObject var extensionManager = ExtensionManager.shared\n\n    @Service var lspService: LSPService\n    @State private var updater: UUID = UUID()\n    @State private var languageServerClients: [LSPService.LanguageServerType] = []\n\n    var body: some View {\n        Picker(\"Output Source\", selection: $selectedSource) {\n            if selectedSource == nil {\n                Text(\"No Selected Output Source\")\n                    .italic()\n                    .tag(Sources?.none)\n                Divider()\n            }\n\n            if languageServerClients.isEmpty {\n                Text(\"No Language Servers\")\n            } else {\n                ForEach(languageServerClients, id: \\.languageId) { server in\n                    Text(Sources.languageServer(server.logContainer).title)\n                        .tag(Sources.languageServer(server.logContainer))\n                }\n            }\n\n            Divider()\n\n            if extensionManager.extensions.isEmpty {\n                Text(\"No Extensions\")\n            } else {\n                ForEach(extensionManager.extensions) { extensionInfo in\n                    Text(Sources.extensions(.init(extensionInfo: extensionInfo)).title)\n                        .tag(Sources.extensions(.init(extensionInfo: extensionInfo)))\n                }\n            }\n\n            if showInternalDevelopmentInspector {\n                Divider()\n                Text(Sources.devOutput.title)\n                    .tag(Sources.devOutput)\n            }\n        }\n        .id(updater)\n        .buttonStyle(.borderless)\n        .labelsHidden()\n        .controlSize(.small)\n        .onAppear {\n            updateLanguageServers(lspService.languageClients)\n        }\n        .onReceive(lspService.$languageClients) { clients in\n            updateLanguageServers(clients)\n        }\n        .onReceive(extensionManager.$extensions) { _ in\n            updater = UUID()\n        }\n    }\n\n    func updateLanguageServers(_ clients: [LSPService.ClientKey: LSPService.LanguageServerType]) {\n        languageServerClients = clients\n            .compactMap { (key, value) in\n                if key.workspacePath == workspace.fileURL?.absolutePath {\n                    return value\n                }\n                return nil\n            }\n            .sorted(by: { $0.languageId.rawValue < $1.languageId.rawValue })\n        if selectedSource == nil, let client = languageServerClients.first {\n            selectedSource = Sources.languageServer(client.logContainer)\n        }\n        updater = UUID()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/OutputUtility/View/UtilityAreaOutputView.swift",
    "content": "//\n//  UtilityAreaOutputView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 5/25/23.\n//\n\nimport SwiftUI\nimport LogStream\n\nstruct UtilityAreaOutputView: View {\n    enum Sources: Hashable {\n        case extensions(ExtensionUtilityAreaOutputSource)\n        case languageServer(LanguageServerLogContainer)\n        case devOutput\n\n        var title: String {\n            switch self {\n            case .extensions(let source):\n                \"Extension - \\(source.extensionInfo.name)\"\n            case .languageServer(let source):\n                \"Language Server - \\(source.id)\"\n            case .devOutput:\n                \"Internal Development Output\"\n            }\n        }\n\n        public static func == (_ lhs: Sources, _ rhs: Sources) -> Bool {\n            switch (lhs, rhs) {\n            case let (.extensions(lhs), .extensions(rhs)):\n                return lhs.id == rhs.id\n            case let (.languageServer(lhs), .languageServer(rhs)):\n                return lhs.id == rhs.id\n            case (.devOutput, .devOutput):\n                return true\n            default:\n                return false\n            }\n        }\n\n        func hash(into hasher: inout Hasher) {\n            switch self {\n            case .extensions(let source):\n                hasher.combine(0)\n                hasher.combine(source.id)\n            case .languageServer(let source):\n                hasher.combine(1)\n                hasher.combine(source.id)\n            case .devOutput:\n                hasher.combine(2)\n            }\n        }\n    }\n\n    @EnvironmentObject private var utilityAreaViewModel: UtilityAreaViewModel\n\n    @State private var filterText: String = \"\"\n    @State private var selectedSource: Sources?\n\n    var body: some View {\n        UtilityAreaTabView(model: utilityAreaViewModel.tabViewModel) { _ in\n            Group {\n                if let selectedSource {\n                    switch selectedSource {\n                    case .extensions(let source):\n                        UtilityAreaOutputLogList(source: source, filterText: $filterText) {\n                            UtilityAreaOutputSourcePicker(selectedSource: $selectedSource)\n                        }\n                    case .languageServer(let source):\n                        UtilityAreaOutputLogList(source: source, filterText: $filterText) {\n                            UtilityAreaOutputSourcePicker(selectedSource: $selectedSource)\n                        }\n                    case .devOutput:\n                        UtilityAreaOutputLogList(\n                            source: InternalDevelopmentOutputSource.shared,\n                            filterText: $filterText\n                        ) {\n                            UtilityAreaOutputSourcePicker(selectedSource: $selectedSource)\n                        }\n                    }\n                } else {\n                    Text(\"No output\")\n                        .font(.system(size: 16))\n                        .foregroundColor(.secondary)\n                        .frame(maxHeight: .infinity)\n                        .paneToolbar {\n                            UtilityAreaOutputSourcePicker(selectedSource: $selectedSource)\n                            Spacer()\n                            UtilityAreaFilterTextField(title: \"Filter\", text: $filterText)\n                                .frame(maxWidth: 175)\n                            Button { } label: {\n                                Image(systemName: \"trash\")\n                            }\n                            .disabled(true)\n                        }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/TerminalUtility/UtilityAreaTerminalPicker.swift",
    "content": "//\n//  UtilityAreaTerminalPicker.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 1/6/25.\n//\n\nimport SwiftUI\n\nstruct UtilityAreaTerminalPicker: View {\n    @Binding var selectedIDs: Set<UUID>\n    var terminals: [UtilityAreaTerminal]\n\n    var selectedID: Binding<UUID?> {\n        Binding<UUID?>(\n            get: {\n                selectedIDs.first\n            },\n            set: { newValue in\n                if let selectedID = newValue {\n                    selectedIDs = [selectedID]\n                }\n            }\n        )\n    }\n\n    var body: some View {\n        Picker(\"Terminal Tab\", selection: selectedID) {\n            ForEach(terminals, id: \\.self.id) { terminal in\n                Text(terminal.title)\n                    .tag(terminal.id)\n            }\n\n            if terminals.isEmpty {\n                Text(\"No Open Terminals\")\n            }\n        }\n        .labelsHidden()\n        .controlSize(.small)\n        .buttonStyle(.borderless)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/TerminalUtility/UtilityAreaTerminalSidebar.swift",
    "content": "//\n//  UtilityAreaTerminalSidebar.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 8/19/24.\n//\n\nimport SwiftUI\n\n/// The view that displays the list of available terminals in the utility area.\n/// See ``UtilityAreaTerminalView`` for use.\nstruct UtilityAreaTerminalSidebar: View {\n    @EnvironmentObject private var workspace: WorkspaceDocument\n    @EnvironmentObject private var utilityAreaViewModel: UtilityAreaViewModel\n\n    var body: some View {\n        List(selection: $utilityAreaViewModel.selectedTerminals) {\n            ForEach(utilityAreaViewModel.terminals, id: \\.self.id) { terminal in\n                UtilityAreaTerminalTab(\n                    terminal: terminal,\n                    removeTerminals: utilityAreaViewModel.removeTerminals,\n                    isSelected: utilityAreaViewModel.selectedTerminals.contains(terminal.id),\n                    selectedIDs: utilityAreaViewModel.selectedTerminals\n                )\n                .tag(terminal.id)\n                .listRowSeparator(.hidden)\n            }\n            .onMove { [weak utilityAreaViewModel] (source, destination) in\n                utilityAreaViewModel?.reorderTerminals(from: source, to: destination)\n            }\n        }\n        .focusedObject(utilityAreaViewModel)\n        .listStyle(.automatic)\n        .accentColor(.secondary)\n        .contextMenu {\n            Button(\"New Terminal\") {\n                utilityAreaViewModel.addTerminal(rootURL: workspace.fileURL)\n            }\n            Menu(\"New Terminal With Profile\") {\n                Button(\"Default\") {\n                    utilityAreaViewModel.addTerminal(rootURL: workspace.fileURL)\n                }\n                Divider()\n                ForEach(Shell.allCases, id: \\.self) { shell in\n                    Button(shell.rawValue) {\n                        utilityAreaViewModel.addTerminal(shell: shell, rootURL: workspace.fileURL)\n                    }\n                }\n            }\n        }\n        .onChange(of: utilityAreaViewModel.terminals) { _, newValue in\n            if newValue.isEmpty {\n                utilityAreaViewModel.addTerminal(rootURL: workspace.fileURL)\n            }\n        }\n        .paneToolbar {\n            PaneToolbarSection {\n                Button {\n                    utilityAreaViewModel.addTerminal(rootURL: workspace.fileURL)\n                } label: {\n                    Image(systemName: \"plus\")\n                }\n                Button {\n                    utilityAreaViewModel.removeTerminals(utilityAreaViewModel.selectedTerminals)\n                } label: {\n                    Image(systemName: \"minus\")\n                }\n                .disabled(utilityAreaViewModel.terminals.count <= 1)\n                .opacity(utilityAreaViewModel.terminals.count <= 1 ? 0.5 : 1)\n            }\n            Spacer()\n        }\n        .accessibilityElement(children: .contain)\n        .accessibilityLabel(\"Terminals\")\n        .accessibilityIdentifier(\"terminalsList\")\n    }\n}\n\n#Preview {\n    UtilityAreaTerminalSidebar()\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/TerminalUtility/UtilityAreaTerminalTab.swift",
    "content": "//\n//  UtilityAreaTerminalTab.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 5/26/23.\n//\n\nimport SwiftUI\n\nstruct UtilityAreaTerminalTab: View {\n    @ObservedObject var terminal: UtilityAreaTerminal\n\n    var removeTerminals: (_ ids: Set<UUID>) -> Void\n\n    var isSelected: Bool\n\n    var selectedIDs: Set<UUID>\n\n    @FocusState private var isFocused: Bool\n\n    var body: some View {\n        let terminalTitle = Binding<String>(\n            get: {\n                self.terminal.title\n            }, set: {\n                if $0.trimmingCharacters(in: .whitespaces) == \"\" && !isFocused {\n                    self.terminal.title = self.terminal.terminalTitle\n                    self.terminal.customTitle = false\n                } else {\n                    self.terminal.title = $0\n                    self.terminal.customTitle = true\n                }\n            }\n        )\n\n        Label {\n            if #available(macOS 14, *) {\n                // Fix the icon misplacement issue introduced since macOS 14\n                TextField(\"Name\", text: terminalTitle)\n                    .focused($isFocused)\n            } else {\n                // A padding is needed for macOS 13\n                TextField(\"Name\", text: terminalTitle)\n                    .focused($isFocused)\n                    .padding(.leading, -8)\n            }\n        } icon: {\n            Image(systemName: \"terminal\")\n        }\n        .contextMenu {\n            Button(\"Rename...\") {\n                isFocused = true\n            }\n\n            if selectedIDs.contains(terminal.id) && selectedIDs.count > 1 {\n                Button(\"Kill Terminals\") {\n                    removeTerminals(selectedIDs)\n                }\n            } else {\n                Button(\"Kill Terminal\") {\n                    removeTerminals([terminal.id])\n                }\n            }\n        }\n        .accessibilityElement(children: .contain)\n        .accessibilityLabel(terminalTitle.wrappedValue)\n        .accessibilityIdentifier(\"terminalTab\")\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/TerminalUtility/UtilityAreaTerminalView.swift",
    "content": "//\n//  UtilityAreaTerminal.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 5/25/23.\n//\n\nimport SwiftUI\nimport Cocoa\n\nstruct UtilityAreaTerminalView: View {\n    @AppSettings(\\.theme.matchAppearance)\n    private var matchAppearance\n    @AppSettings(\\.terminal.darkAppearance)\n    private var darkAppearance\n    @AppSettings(\\.theme.useThemeBackground)\n    private var useThemeBackground\n    @AppSettings(\\.textEditing.font)\n    private var textEditingFont\n    @AppSettings(\\.terminal.font)\n    private var terminalFont\n    @AppSettings(\\.terminal.useTextEditorFont)\n    private var useTextEditorFont\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @EnvironmentObject private var workspace: WorkspaceDocument\n\n    @EnvironmentObject private var utilityAreaViewModel: UtilityAreaViewModel\n\n    @State private var sidebarIsCollapsed = false\n\n    @StateObject private var themeModel: ThemeModel = .shared\n\n    @State private var isMenuVisible = false\n\n    @State private var popoverSource: CGRect = .zero\n\n    var font: NSFont {\n        useTextEditorFont == true ? textEditingFont.current : terminalFont.current\n    }\n\n    /// Returns the `background` color of the selected theme\n    private var backgroundColor: NSColor {\n        if let selectedTheme = matchAppearance && darkAppearance\n            ? themeModel.selectedDarkTheme\n            : themeModel.selectedTheme,\n           let index = themeModel.themes.firstIndex(of: selectedTheme) {\n            return NSColor(themeModel.themes[index].terminal.background.swiftColor)\n        }\n        return .windowBackgroundColor\n    }\n\n    /// Decides the color scheme used in the terminal.\n    ///\n    /// Decision list:\n    /// - If there is no selection, use the system color scheme ``UtilityAreaTerminalView/colorScheme``\n    /// - If the match appearance and dark appearance settings are true, return dark if the selected dark theme is dark.\n    /// - Otherwise, return dark if the selected theme is dark.\n    private var terminalColorScheme: ColorScheme {\n        return if utilityAreaViewModel.selectedTerminals.isEmpty {\n            colorScheme\n        } else if matchAppearance && darkAppearance {\n            themeModel.selectedDarkTheme?.appearance == .dark ? .dark : .light\n        } else {\n            themeModel.selectedTheme?.appearance == .dark ? .dark : .light\n        }\n    }\n\n    /// Finds the selected terminal.\n    /// - Returns: The selected terminal.\n    private func getSelectedTerminal() -> UtilityAreaTerminal? {\n        guard let selectedTerminalID = utilityAreaViewModel.selectedTerminals.first else {\n            return nil\n        }\n        return utilityAreaViewModel.terminals.first(where: { $0.id == selectedTerminalID })\n    }\n\n    /// Estimate the font's height for keeping the terminal aligned with the bottom.\n    /// - Parameter nsFont: The font being used in the terminal.\n    /// - Returns: The height in pixels of the font.\n    func fontTotalHeight(nsFont: NSFont) -> CGFloat {\n        let ctFont = nsFont as CTFont\n        let ascent = CTFontGetAscent(ctFont)\n        let descent = CTFontGetDescent(ctFont)\n        let leading = CTFontGetLeading(ctFont)\n        return ascent + descent + leading\n    }\n\n    var body: some View {\n        UtilityAreaTabView(model: utilityAreaViewModel.tabViewModel) { tabState in\n            ZStack {\n                // Keeps the sidebar from changing sizes because TerminalEmulatorView takes a µs to load in\n                HStack { Spacer() }\n\n                if let selectedTerminal = getSelectedTerminal() {\n                    GeometryReader { geometry in\n                        let containerHeight = geometry.size.height\n                        let totalFontHeight = fontTotalHeight(nsFont: font).rounded(.up)\n                        let constrainedHeight = containerHeight - containerHeight.truncatingRemainder(\n                            dividingBy: totalFontHeight\n                        )\n                        VStack(spacing: 0) {\n                            Spacer(minLength: 0).frame(minHeight: 0)\n                            TerminalEmulatorView(\n                                url: selectedTerminal.url,\n                                terminalID: selectedTerminal.id,\n                                shellType: selectedTerminal.shell,\n                                onTitleChange: { [weak selectedTerminal] newTitle in\n                                    guard let id = selectedTerminal?.id else { return }\n                                    // This can be called whenever, even in a view update so it needs to be dispatched.\n                                    DispatchQueue.main.async { [weak utilityAreaViewModel] in\n                                        utilityAreaViewModel?.updateTerminal(id, title: newTitle)\n                                    }\n                                }\n                            )\n                            .frame(height: max(0, constrainedHeight - 1))\n                            .id(selectedTerminal.id)\n                            .accessibilityIdentifier(\"terminal\")\n                        }\n                    }\n                } else {\n                    CEContentUnavailableView(\"No Selection\")\n                }\n            }\n            .padding(.horizontal, 10)\n            .paneToolbar {\n                PaneToolbarSection {\n                    UtilityAreaTerminalPicker(\n                        selectedIDs: $utilityAreaViewModel.selectedTerminals,\n                        terminals: utilityAreaViewModel.terminals\n                    )\n                    .opacity(tabState.leadingSidebarIsCollapsed ? 1 : 0)\n                }\n                Spacer()\n                PaneToolbarSection {\n                    Button {\n                        guard let terminal = getSelectedTerminal() else {\n                            return\n                        }\n                        utilityAreaViewModel.replaceTerminal(terminal.id)\n                    } label: {\n                        Image(systemName: \"trash\")\n                    }\n                    .help(\"Reset the terminal\")\n                    .disabled(getSelectedTerminal() == nil)\n                    Button {\n                        // split terminal\n                    } label: {\n                        Image(systemName: \"square.split.2x1\")\n                    }\n                    .help(\"Implementation Needed\")\n                    .disabled(true)\n                }\n            }\n            .background {\n                backgroundEffectView\n            }\n            .colorScheme(terminalColorScheme)\n        } leadingSidebar: { _ in\n            UtilityAreaTerminalSidebar()\n        }\n        .onAppear {\n            guard let workspaceURL = workspace.fileURL else {\n                assertionFailure(\"Workspace does not have a file URL.\")\n                return\n            }\n            utilityAreaViewModel.initializeTerminals(workspaceURL: workspaceURL)\n        }\n        .accessibilityIdentifier(\"terminal-area\")\n    }\n\n    @ViewBuilder var backgroundEffectView: some View {\n        if utilityAreaViewModel.selectedTerminals.isEmpty {\n            EffectView(.contentBackground)\n        } else if useThemeBackground {\n            Color(nsColor: backgroundColor)\n        } else {\n            if colorScheme == .dark {\n                EffectView(.underPageBackground)\n            } else {\n                EffectView(.contentBackground)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/Toolbar/UtilityAreaClearButton.swift",
    "content": "//\n//  UtilityAreaClearButton.swift\n//  CodeEdit\n//\n//  Created by Stef Kors on 12/04/2022.\n//\n\nimport SwiftUI\n\nstruct UtilityAreaClearButton: View {\n    var body: some View {\n        Button {\n            // Clear terminal\n        } label: {\n            Image(systemName: \"trash\")\n                .foregroundColor(.secondary)\n        }\n        .buttonStyle(.plain)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/Toolbar/UtilityAreaFilterTextField.swift",
    "content": "//\n//  UtilityAreaFilterTextField.swift\n//  CodeEdit\n//\n//  Created by Stef Kors on 12/04/2022.\n//\n\nimport SwiftUI\n\nstruct UtilityAreaFilterTextField: View {\n    let title: String\n\n    @Binding var text: String\n\n    @FocusState private var isFocused: Bool\n\n    var body: some View {\n        HStack(spacing: 5) {\n            Image(systemName: \"line.3.horizontal.decrease.circle\")\n                .foregroundColor(Color(nsColor: .labelColor))\n                .font(.system(size: 13, weight: .regular))\n                .padding(.leading, -1)\n                .padding(.trailing, -2)\n            textField\n            if !text.isEmpty { clearButton }\n        }\n        .padding(.horizontal, 5)\n        .frame(height: 22)\n        .background(Color(nsColor: isFocused ? .textBackgroundColor : .quaternaryLabelColor))\n        .clipShape(RoundedRectangle(cornerRadius: 7))\n        .overlay(\n            RoundedRectangle(cornerRadius: 7)\n                .stroke(isFocused ? .secondary : .tertiary, lineWidth: 0.75)\n                .clipShape(RoundedRectangle(cornerRadius: 7))\n        )\n    }\n\n    private var textField: some View {\n        TextField(title, text: $text)\n            .font(.system(size: 11, weight: .regular))\n            .disableAutocorrection(true)\n            .textFieldStyle(PlainTextFieldStyle())\n            .focused($isFocused)\n    }\n\n    private var clearButton: some View {\n        Button {\n            self.text = \"\"\n        } label: {\n            Image(systemName: \"xmark.circle.fill\")\n        }\n        .foregroundColor(.secondary)\n        .buttonStyle(PlainButtonStyle())\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/Toolbar/UtilityAreaMaximizeButton.swift",
    "content": "//\n//  UtilityAreaMaximizeButton.swift\n//  CodeEdit\n//\n//  Created by Stef Kors on 12/04/2022.\n//\n\nimport SwiftUI\n\nstruct UtilityAreaMaximizeButton: View {\n    @EnvironmentObject private var utilityAreaViewModel: UtilityAreaViewModel\n\n    var body: some View {\n        Button {\n            utilityAreaViewModel.isMaximized.toggle()\n        } label: {\n            Image(systemName: \"arrowtriangle.up.square\")\n                .foregroundColor(utilityAreaViewModel.isMaximized ? .accentColor : .secondary)\n        }\n        .buttonStyle(.plain)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/Toolbar/UtilityAreaSplitTerminalButton.swift",
    "content": "//\n//  UtilityAreaSplitTerminalButton.swift\n//  CodeEdit\n//\n//  Created by Stef Kors on 14/04/2022.\n//\n\nimport SwiftUI\n\nstruct UtilityAreaSplitTerminalButton: View {\n    var body: some View {\n        Button {\n            // todo\n        } label: {\n            Image(systemName: \"square.split.2x1\")\n                .foregroundColor(.secondary)\n        }\n        .buttonStyle(.plain)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/ViewModels/UtilityAreaTabViewModel.swift",
    "content": "//\n//  UtilityAreaTabViewModel.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 5/31/23.\n//\n\nimport SwiftUI\n\nclass UtilityAreaTabViewModel: ObservableObject {\n    @Published var leadingSidebarIsCollapsed: Bool = false\n\n    @Published var trailingSidebarIsCollapsed: Bool = false\n\n    @Published var hasLeadingSidebar: Bool = false\n\n    @Published var hasTrailingSidebar: Bool = false\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/ViewModels/UtilityAreaViewModel.swift",
    "content": "//\n//  UtilityAreaViewModel.swift\n//  CodeEdit\n//\n//  Created by Lukas Pistrol on 20.03.22.\n//\n\nimport SwiftUI\n\n/// # UtilityAreaViewModel\n///\n/// A model class to host and manage data for the Utility area.\nclass UtilityAreaViewModel: ObservableObject {\n\n    @Published var selectedTab: UtilityAreaTab? = .terminal\n\n    @Published var terminals: [UtilityAreaTerminal] = []\n\n    @Published var selectedTerminals: Set<UtilityAreaTerminal.ID> = []\n\n    /// Indicates whether debugger is collapse or not\n    @Published var isCollapsed: Bool = false\n\n    /// Indicates whether collapse animation should be enabled when utility area is toggled\n    @Published var animateCollapse: Bool = true\n\n    /// Returns true when the drawer is visible\n    @Published var isMaximized: Bool = false\n\n    /// The current height of the drawer. Zero if hidden\n    @Published var currentHeight: Double = 0\n\n    /// The tab bar items for the UtilityAreaView\n    @Published var tabItems: [UtilityAreaTab] = UtilityAreaTab.allCases\n\n    /// The tab bar view model for UtilityAreaTabView\n    @Published var tabViewModel = UtilityAreaTabViewModel()\n\n    // MARK: - State Restoration\n\n    func restoreFromState(_ workspace: WorkspaceDocument) {\n        isCollapsed = workspace.getFromWorkspaceState(.utilityAreaCollapsed) as? Bool ?? false\n        currentHeight = workspace.getFromWorkspaceState(.utilityAreaHeight) as? Double ?? 300.0\n        isMaximized = workspace.getFromWorkspaceState(.utilityAreaMaximized) as? Bool ?? false\n    }\n\n    func saveRestorationState(_ workspace: WorkspaceDocument) {\n        workspace.addToWorkspaceState(key: .utilityAreaCollapsed, value: isCollapsed)\n        workspace.addToWorkspaceState(key: .utilityAreaHeight, value: currentHeight)\n        workspace.addToWorkspaceState(key: .utilityAreaMaximized, value: isMaximized)\n    }\n\n    func togglePanel(animation: Bool = true) {\n        self.animateCollapse = animation\n        self.isMaximized = false\n        self.isCollapsed.toggle()\n    }\n\n    // MARK: - Terminal Management\n\n    /// Removes all terminals included in the given set and selects a new terminal if the selection was modified.\n    /// The new selection is either the same selection minus the ids removed, or if that's empty the last terminal.\n    /// - Parameter ids: A set of all terminal ids to remove.\n    func removeTerminals(_ ids: Set<UUID>) {\n        for (idx, terminal) in terminals.enumerated().reversed()\n        where ids.contains(terminal.id) {\n            TerminalCache.shared.removeCachedView(terminal.id)\n            terminals.remove(at: idx)\n        }\n\n        var newSelection = selectedTerminals.subtracting(ids)\n\n        if newSelection.isEmpty, let terminal = terminals.last {\n            newSelection = [terminal.id]\n        }\n\n        selectedTerminals = newSelection\n    }\n\n    /// Update a terminal's title.\n    /// - Parameters:\n    ///   - id: The id of the terminal to update.\n    ///   - title: The title to set. If left `nil`, will set the terminal's\n    ///            ``UtilityAreaTerminal/customTitle`` to `false`.\n    func updateTerminal(_ id: UUID, title: String?) {\n        guard let terminal = terminals.first(where: { $0.id == id }) else { return }\n        if let newTitle = title {\n            if !terminal.customTitle {\n                terminal.title = newTitle\n            }\n            terminal.terminalTitle = newTitle\n        } else {\n            terminal.customTitle = false\n        }\n    }\n\n    /// Create a new terminal if there are no existing terminals.\n    /// Will not perform any action if terminals exist in the ``terminals`` array.\n    /// - Parameter workspaceURL: The base url of the workspace, to initialize terminals.l\n    func initializeTerminals(workspaceURL: URL) {\n        guard terminals.isEmpty else { return }\n        addTerminal(rootURL: workspaceURL)\n    }\n\n    /// Add a new terminal to the workspace and selects it.\n    /// - Parameters:\n    ///   - shell: The shell to use, `nil` if auto-detect the default shell.\n    ///   - rootURL: The url to start the new terminal at. If left `nil` defaults to the user's home directory.\n    func addTerminal(shell: Shell? = nil, rootURL: URL?) {\n        let id = UUID()\n\n        terminals.append(\n            UtilityAreaTerminal(\n                id: id,\n                url: rootURL ?? URL(filePath: \"~/\"),\n                title: shell?.rawValue ?? \"terminal\",\n                shell: shell\n            )\n        )\n\n        selectedTerminals = [id]\n    }\n\n    /// Replaces the terminal with a given ID, killing the shell and restarting it at the same directory.\n    ///\n    /// Terminals being replaced will have the `SIGKILL` signal sent to the running shell. The new terminal will\n    /// inherit the same `url` and `shell` parameters from the old one.\n    /// - Parameter replacing: The ID of a terminal to replace with a new terminal.\n    func replaceTerminal(_ replacing: UUID) {\n        guard let index = terminals.firstIndex(where: { $0.id == replacing }) else {\n            return\n        }\n\n        let id = UUID()\n        let url = terminals[index].url\n        let shell = terminals[index].shell\n        if let shellPid = TerminalCache.shared.getTerminalView(replacing)?.process.shellPid {\n            kill(shellPid, SIGKILL)\n        }\n\n        terminals[index] = UtilityAreaTerminal(\n            id: id,\n            url: url,\n            title: shell?.rawValue ?? \"terminal\",\n            shell: shell\n        )\n        TerminalCache.shared.removeCachedView(replacing)\n\n        selectedTerminals = [id]\n        return\n    }\n\n    /// Reorders terminals in the ``utilityAreaViewModel``.\n    /// - Parameters:\n    ///   - source: The source indices.\n    ///   - destination: The destination indices.\n    func reorderTerminals(from source: IndexSet, to destination: Int) {\n        terminals.move(fromOffsets: source, toOffset: destination)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/Views/PaneToolbar.swift",
    "content": "//\n//  PaneToolbar.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 5/31/23.\n//\n\nimport SwiftUI\n\nstruct PaneToolbar<Content: View>: View {\n    @ViewBuilder var content: Content\n    @EnvironmentObject var model: UtilityAreaTabViewModel\n    @Environment(\\.paneArea)\n    var paneArea: PaneArea?\n\n    var body: some View {\n        HStack(spacing: 5) {\n            if model.hasLeadingSidebar\n                && (\n                    ((paneArea == .main || paneArea == .mainLeading)\n                        && model.leadingSidebarIsCollapsed)\n                    || paneArea == .leading\n                ) {\n                PaneToolbarSection {\n                    Spacer()\n                        .frame(width: 24)\n                }\n                .opacity(0)\n            }\n            content\n            if model.hasTrailingSidebar\n                && (\n                    ((paneArea == .main || paneArea == .mainTrailing)\n                        && model.trailingSidebarIsCollapsed)\n                    || paneArea == .trailing\n                ) || !model.hasTrailingSidebar {\n                if model.hasTrailingSidebar {\n                    PaneToolbarSection {\n                        Spacer()\n                            .frame(width: 24)\n                    }\n                    .opacity(0)\n                }\n            }\n        }\n        .buttonStyle(.icon(size: 24))\n        .padding(.horizontal, 5)\n        .padding(.vertical, 8)\n        .frame(maxHeight: 27)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/Views/UtilityAreaTabView.swift",
    "content": "//\n//  UtilityAreaTabView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 5/30/23.\n//\n\nimport SwiftUI\n\nstruct UtilityAreaTabView<Content: View, LeadingSidebar: View, TrailingSidebar: View>: View {\n    @ObservedObject var model: UtilityAreaTabViewModel\n\n    let content: (UtilityAreaTabViewModel) -> Content\n    let leadingSidebar: (UtilityAreaTabViewModel) -> LeadingSidebar?\n    let trailingSidebar: (UtilityAreaTabViewModel) -> TrailingSidebar?\n\n    let hasLeadingSidebar: Bool\n    let hasTrailingSidebar: Bool\n\n    init(\n        model: UtilityAreaTabViewModel,\n        @ViewBuilder content: @escaping (UtilityAreaTabViewModel) -> Content,\n        @ViewBuilder leadingSidebar: @escaping (UtilityAreaTabViewModel) -> LeadingSidebar,\n        @ViewBuilder trailingSidebar: @escaping (UtilityAreaTabViewModel) -> TrailingSidebar,\n        hasLeadingSidebar: Bool = true,\n        hasTrailingSidebar: Bool = true\n    ) {\n        self.model = model\n\n        self.content = content\n        self.leadingSidebar = leadingSidebar\n        self.trailingSidebar = trailingSidebar\n\n        self.hasLeadingSidebar = hasLeadingSidebar\n        self.hasTrailingSidebar = hasTrailingSidebar\n    }\n\n    init(\n        model: UtilityAreaTabViewModel,\n        @ViewBuilder content: @escaping (UtilityAreaTabViewModel) -> Content\n    ) where\n        LeadingSidebar == EmptyView,\n        TrailingSidebar == EmptyView {\n        self.init(\n            model: model,\n            content: content,\n            leadingSidebar: { _ in EmptyView() },\n            trailingSidebar: { _ in EmptyView() },\n            hasLeadingSidebar: false,\n            hasTrailingSidebar: false\n        )\n    }\n\n    init(\n        model: UtilityAreaTabViewModel,\n        @ViewBuilder content: @escaping (UtilityAreaTabViewModel) -> Content,\n        @ViewBuilder leadingSidebar: @escaping (UtilityAreaTabViewModel) -> LeadingSidebar\n    ) where TrailingSidebar == EmptyView {\n        self.init(\n            model: model,\n            content: content,\n            leadingSidebar: leadingSidebar,\n            trailingSidebar: { _ in EmptyView() },\n            hasTrailingSidebar: false\n        )\n    }\n\n    init(\n        model: UtilityAreaTabViewModel,\n        @ViewBuilder content: @escaping (UtilityAreaTabViewModel) -> Content,\n        @ViewBuilder trailingSidebar: @escaping (UtilityAreaTabViewModel) -> TrailingSidebar\n    ) where LeadingSidebar == EmptyView {\n        self.init(\n            model: model,\n            content: content,\n            leadingSidebar: { _ in EmptyView() },\n            trailingSidebar: trailingSidebar,\n            hasLeadingSidebar: false\n        )\n    }\n\n    var body: some View {\n        SplitView(axis: .horizontal) {\n            // Leading Sidebar\n            if model.hasLeadingSidebar {\n                leadingSidebar(model)\n                    .collapsable()\n                    .collapsed($model.leadingSidebarIsCollapsed)\n                    .frame(minWidth: 200, idealWidth: 240, maxWidth: 400)\n                    .environment(\\.paneArea, .leading)\n            }\n\n            // Content Area\n            content(model)\n                .holdingPriority(.init(1))\n                .environment(\\.paneArea, .main)\n\n            // Trailing Sidebar\n            if model.hasTrailingSidebar {\n                trailingSidebar(model)\n                    .collapsable()\n                    .collapsed($model.trailingSidebarIsCollapsed)\n                    .frame(minWidth: 200, idealWidth: 240, maxWidth: 400)\n                    .environment(\\.paneArea, .trailing)\n            }\n        }\n        .animation(.default, value: model.leadingSidebarIsCollapsed)\n        .animation(.default, value: model.trailingSidebarIsCollapsed)\n        .frame(maxHeight: .infinity)\n        .overlay(alignment: .bottomLeading) {\n            if model.hasLeadingSidebar {\n                PaneToolbar {\n                    PaneToolbarSection {\n                        Button {\n                            model.leadingSidebarIsCollapsed.toggle()\n                        } label: {\n                            Image(systemName: \"square.leadingthird.inset.filled\")\n                        }\n                        .buttonStyle(.icon(isActive: !model.leadingSidebarIsCollapsed))\n                    }\n                    Divider()\n                }\n            }\n        }\n        .overlay(alignment: .bottomTrailing) {\n            if model.hasTrailingSidebar {\n                PaneToolbar {\n                    Divider()\n                    PaneToolbarSection {\n                        Button {\n                            model.trailingSidebarIsCollapsed.toggle()\n                        } label: {\n                            Image(systemName: \"square.trailingthird.inset.filled\")\n                        }\n                        .buttonStyle(.icon(isActive: !model.trailingSidebarIsCollapsed))\n                        Spacer()\n                            .frame(width: 24)\n                    }\n                }\n            }\n        }\n        .environmentObject(model)\n        .onAppear {\n            model.hasLeadingSidebar = hasLeadingSidebar\n            model.hasTrailingSidebar = hasTrailingSidebar\n        }\n    }\n}\n\nenum PaneArea: String {\n    case leading\n    case main\n    case mainLeading\n    case mainCenter\n    case mainTrailing\n    case trailing\n}\n\nprivate struct PaneAreaKey: EnvironmentKey {\n    static let defaultValue: PaneArea? = nil\n}\n\nextension EnvironmentValues {\n    var paneArea: PaneArea? {\n        get { self[PaneAreaKey.self] }\n        set { self[PaneAreaKey.self] = newValue }\n    }\n}\n\nstruct PaneToolbarSection<Content: View>: View {\n    @ViewBuilder var content: Content\n\n    var body: some View {\n        HStack(spacing: 0) {\n            content\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/Views/UtilityAreaView.swift",
    "content": "//\n//  UtilityAreaView.swift\n//  CodeEdit\n//\n//  Created by Lukas Pistrol on 22.03.22.\n//\n\nimport SwiftUI\n\nstruct UtilityAreaView: View {\n    @EnvironmentObject private var utilityAreaViewModel: UtilityAreaViewModel\n\n    var body: some View {\n        WorkspacePanelView(\n            viewModel: utilityAreaViewModel,\n            selectedTab: $utilityAreaViewModel.selectedTab,\n            tabItems: $utilityAreaViewModel.tabItems,\n            sidebarPosition: .side,\n            darkDivider: true\n        )\n        .accessibilityElement(children: .contain)\n        .accessibilityLabel(\"Utility Area\")\n        .accessibilityIdentifier(\"UtilityArea\")\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/UtilityArea/Views/View+paneToolbar.swift",
    "content": "//\n//  View+paneToolbar.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 5/31/23.\n//\n\nimport SwiftUI\n\nextension View {\n    /// Clips and adds a bottom toolbar to the view.\n    /// - Parameter content: The content of the toolbar.\n    func paneToolbar<Content: View>(@ViewBuilder content: () -> Content) -> some View {\n        self\n            .clipped()\n            .safeAreaInset(edge: .bottom, spacing: 0) {\n                PaneToolbar {\n                    content()\n                }\n            }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Welcome/GitCloneButton.swift",
    "content": "//\n//  GitCloneButton.swift\n//  CodeEdit\n//\n//  Created by Giorgi Tchelidze on 07.06.25.\n//\n\nimport SwiftUI\nimport WelcomeWindow\n\nstruct GitCloneButton: View {\n\n    @State private var showGitClone = false\n    @State private var showCheckoutBranchItem: URL?\n\n    var dismissWindow: () -> Void\n\n    var body: some View {\n        WelcomeButton(\n            iconName: \"square.and.arrow.down.on.square\",\n            title: \"Clone Git Repository...\",\n            action: {\n                showGitClone = true\n            }\n        )\n        .sheet(isPresented: $showGitClone) {\n            GitCloneView(\n                openBranchView: { url in\n                    showCheckoutBranchItem = url\n                },\n                openDocument: { url in\n                    CodeEditDocumentController.shared.openDocument(at: url, onCompletion: { dismissWindow() })\n                }\n            )\n        }\n        .sheet(item: $showCheckoutBranchItem) { url in\n            GitCheckoutBranchView(\n                repoLocalPath: url,\n                openDocument: { url in\n                    CodeEditDocumentController.shared.openDocument(at: url, onCompletion: { dismissWindow() })\n                }\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Welcome/NewFileButton.swift",
    "content": "//\n//  NewFileButton.swift\n//  CodeEdit\n//\n//  Created by Giorgi Tchelidze on 07.06.25.\n//\n\nimport SwiftUI\nimport WelcomeWindow\n\nstruct NewFileButton: View {\n\n    var dismissWindow: () -> Void\n\n    var body: some View {\n        WelcomeButton(\n            iconName: \"plus.square\",\n            title: \"Create New File...\",\n            action: {\n                let documentController = CodeEditDocumentController()\n                documentController.createAndOpenNewDocument(onCompletion: { dismissWindow() })\n            }\n        )\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Welcome/OpenFileOrFolderButton.swift",
    "content": "//\n//  OpenFileOrFolderButton.swift\n//  CodeEdit\n//\n//  Created by Giorgi Tchelidze on 07.06.25.\n//\n\nimport SwiftUI\nimport WelcomeWindow\n\nstruct OpenFileOrFolderButton: View {\n\n    @Environment(\\.openWindow)\n    private var openWindow\n\n    var dismissWindow: () -> Void\n\n    var body: some View {\n        WelcomeButton(\n            iconName: \"folder\",\n            title: \"Open File or Folder...\",\n            action: {\n                CodeEditDocumentController.shared.openDocumentWithDialog(\n                    configuration: .init(canChooseFiles: true, canChooseDirectories: true),\n                    onDialogPresented: { dismissWindow() },\n                    onCancel: { openWindow(id: DefaultSceneID.welcome) }\n                )\n            }\n        )\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/Welcome/WelcomeSubtitleView.swift",
    "content": "//\n//  WelcomeSubtitleView.swift\n//  CodeEdit\n//\n//  Created by Giorgi Tchelidze on 07.06.25.\n//\n\nimport SwiftUI\nimport WelcomeWindow\n\nstruct WelcomeSubtitleView: View {\n\n    private var appVersion: String { Bundle.versionString ?? \"\" }\n    private var appBuild: String { Bundle.buildString ?? \"\" }\n    private var appVersionPostfix: String { Bundle.versionPostfix ?? \"\" }\n\n    private var macOSVersion: String {\n        let url = URL(fileURLWithPath: \"/System/Library/CoreServices/SystemVersion.plist\")\n        guard let dict = NSDictionary(contentsOf: url),\n              let version = dict[\"ProductUserVisibleVersion\"],\n              let build = dict[\"ProductBuildVersion\"] else {\n            return ProcessInfo.processInfo.operatingSystemVersionString\n        }\n        return \"\\(version) (\\(build))\"\n    }\n\n    private var xcodeVersion: String? {\n        guard let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: \"com.apple.dt.Xcode\"),\n              let bundle = Bundle(url: url),\n              let infoDict = bundle.infoDictionary,\n              let version = infoDict[\"CFBundleShortVersionString\"] as? String,\n              let buildURL = URL(string: \"\\(url)Contents/version.plist\"),\n              let buildDict = try? NSDictionary(contentsOf: buildURL, error: ()),\n              let build = buildDict[\"ProductBuildVersion\"]\n        else {\n            return nil\n        }\n        return \"\\(version) (\\(build))\"\n    }\n\n    private func copyInformation() {\n        var copyString = \"\\(Bundle.displayName): \\(appVersion)\\(appVersionPostfix) (\\(appBuild))\\n\"\n        copyString.append(\"macOS: \\(macOSVersion)\\n\")\n        if let xcodeVersion { copyString.append(\"Xcode: \\(xcodeVersion)\") }\n\n        let pasteboard = NSPasteboard.general\n        pasteboard.clearContents()\n        pasteboard.setString(copyString, forType: .string)\n    }\n\n    var body: some View {\n        Text(String(\n            format: NSLocalizedString(\"Version %@%@ (%@)\", comment: \"\"),\n            appVersion, appVersionPostfix, appBuild\n        ))\n        .textSelection(.enabled)\n        .onHover { $0 ? NSCursor.pointingHand.push() : NSCursor.pop() }\n        .onTapGesture { copyInformation() }\n        .help(\"Copy System Information to Clipboard\")\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/WindowCommands/CodeEditCommands.swift",
    "content": "//\n//  CodeEditCommands.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 11/03/2023.\n//\n\nimport SwiftUI\n\nstruct CodeEditCommands: Commands {\n    @AppSettings(\\.sourceControl.general.sourceControlIsEnabled)\n    private var sourceControlIsEnabled\n\n    var body: some Commands {\n        Group { // SwiftUI limits to 9 items in an initializer, so we have to group every 9 items.\n            MainCommands()\n            FileCommands()\n            ViewCommands()\n            FindCommands()\n            NavigateCommands()\n            TasksCommands()\n            if sourceControlIsEnabled { SourceControlCommands() }\n            EditorCommands()\n            ExtensionCommands()\n            WindowCommands()\n        }\n        HelpCommands()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/WindowCommands/EditorCommands.swift",
    "content": "//\n//  EditorCommands.swift\n//  CodeEdit\n//\n//  Created by Bogdan Belogurov on 21/05/2025.\n//\n\nimport SwiftUI\nimport CodeEditKit\n\nstruct EditorCommands: Commands {\n\n    @UpdatingWindowController var windowController: CodeEditWindowController?\n    private var editor: Editor? {\n        windowController?.workspace?.editorManager?.activeEditor\n    }\n\n    var body: some Commands {\n        CommandMenu(\"Editor\") {\n            Menu(\"Structure\") {\n                Button(\"Move line up\") {\n                    editor?.selectedTab?.rangeTranslator.moveLinesUp()\n                }\n                .keyboardShortcut(\"[\", modifiers: [.command, .option])\n\n                Button(\"Move line down\") {\n                    editor?.selectedTab?.rangeTranslator.moveLinesDown()\n                }\n                .keyboardShortcut(\"]\", modifiers: [.command, .option])\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/WindowCommands/ExtensionCommands.swift",
    "content": "//\n//  ExtensionCommands.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 24/03/2023.\n//\n\nimport SwiftUI\nimport CodeEditKit\n\nstruct ExtensionCommands: Commands {\n    @FocusedObject var manager: ExtensionManager?\n\n    @Environment(\\.openWindow)\n    var openWindow\n\n    var body: some Commands {\n        CommandMenu(\"Extensions\") {\n            Button(\"Open Extensions Window\") {\n                openWindow(sceneID: .extensions)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/WindowCommands/FileCommands.swift",
    "content": "//\n//  FileCommands.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 13/03/2023.\n//\n\nimport SwiftUI\n\nstruct FileCommands: Commands {\n    static let recentProjectsMenu = RecentProjectsMenu()\n\n    @Environment(\\.openWindow)\n    private var openWindow\n\n    @UpdatingWindowController var windowController\n\n    @FocusedObject var utilityAreaViewModel: UtilityAreaViewModel?\n\n    var body: some Commands {\n        CommandGroup(replacing: .newItem) {\n            Group {\n                Button(\"New\") {\n                    NSDocumentController.shared.newDocument(nil)\n                }\n                .keyboardShortcut(\"n\")\n\n                Button(\"Open...\") {\n                    NSDocumentController.shared.openDocument(nil)\n                }\n                .keyboardShortcut(\"o\")\n\n                // Leave this empty, is done through a hidden API in WindowCommands/Utils/CommandsFixes.swift\n                // We set this with a custom NSMenu. See WindowCommands/Utils/RecentProjectsMenu.swift\n                Menu(\"Open Recent\") { }\n\n                Button(\"Open Quickly\") {\n                    NSApp.sendAction(#selector(CodeEditWindowController.openQuickly(_:)), to: nil, from: nil)\n                }\n                .keyboardShortcut(\"o\", modifiers: [.command, .shift])\n            }\n        }\n\n        CommandGroup(replacing: .saveItem) {\n            Button(\"Close Tab\") {\n                if NSApp.target(forAction: #selector(CodeEditWindowController.closeCurrentTab(_:))) != nil {\n                    NSApp.sendAction(#selector(CodeEditWindowController.closeCurrentTab(_:)), to: nil, from: nil)\n                } else {\n                    NSApp.sendAction(#selector(NSWindow.performClose(_:)), to: NSApp.keyWindow, from: nil)\n                }\n            }\n            .keyboardShortcut(\"w\")\n\n            Button(\"Close Editor\") {\n                if NSApp.target(forAction: #selector(CodeEditWindowController.closeActiveEditor(_:))) != nil {\n                    NSApp.sendAction(\n                        #selector(CodeEditWindowController.closeActiveEditor(_:)),\n                        to: nil,\n                        from: nil\n                    )\n                } else {\n                    NSApp.sendAction(#selector(NSWindow.performClose(_:)), to: NSApp.keyWindow, from: nil)\n                }\n            }\n            .keyboardShortcut(\"w\", modifiers: [.control, .shift, .command])\n\n            Button(\"Close Window\") {\n                NSApp.sendAction(#selector(NSWindow.performClose(_:)), to: NSApp.keyWindow, from: nil)\n            }\n            .keyboardShortcut(\"w\", modifiers: [.shift, .command])\n\n            Button(\"Close Workspace\") {\n                NSApp.sendAction(#selector(NSWindow.performClose(_:)), to: NSApp.keyWindow, from: nil)\n            }\n            .keyboardShortcut(\"w\", modifiers: [.control, .option, .command])\n            .disabled(!(NSApplication.shared.keyWindow?.windowController is CodeEditWindowController))\n\n            if let utilityAreaViewModel {\n                Button(\"Close Terminal\") {\n                    utilityAreaViewModel.removeTerminals(utilityAreaViewModel.selectedTerminals)\n                }\n                .keyboardShortcut(.delete)\n            }\n\n            Divider()\n\n            Button(\"Workspace Settings\") {\n                NSApp.sendAction(#selector(CodeEditWindowController.openWorkspaceSettings(_:)), to: nil, from: nil)\n            }\n            .disabled(windowController?.workspace == nil)\n\n            Divider()\n\n            Button(\"Save\") {\n                NSApp.sendAction(#selector(CodeEditWindowController.saveDocument(_:)), to: nil, from: nil)\n            }\n            .keyboardShortcut(\"s\")\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/WindowCommands/FindCommands.swift",
    "content": "//\n//  FindCommands.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 13/03/2023.\n//\n\nimport SwiftUI\n\nstruct FindCommands: Commands {\n\n    @FirstResponder var responder\n\n    static let selector = #selector(NSTextView.performFindPanelAction(_:))\n\n    var hasResponder: Bool {\n        responder?.responds(to: Self.selector) ?? false\n    }\n\n    var body: some Commands {\n        CommandMenu(\"Find\") {\n            Group {\n                Button(\"Find...\") {\n                    send(.showFindPanel)\n                }\n                .keyboardShortcut(\"f\")\n\n                Button(\"Find and Replace...\") {\n                    send(.init(rawValue: 12)!)\n                }\n                .keyboardShortcut(\"f\", modifiers: [.option, .command])\n\n                Button(\"Find Next\") {\n                    send(.next)\n                }\n                .keyboardShortcut(\"g\")\n\n                Button(\"Find Previous\") {\n                    send(.previous)\n                }\n                .keyboardShortcut(\"g\", modifiers: [.shift, .command])\n\n                Button(\"Use Selection for Find\") {\n                    send(.setFindString)\n                }\n                .keyboardShortcut(\"e\")\n\n                Button(\"Jump to Selection\") {\n                    NSApp.sendAction(#selector(NSTextView.centerSelectionInVisibleArea(_:)), to: nil, from: nil)\n                }\n                .keyboardShortcut(\"j\")\n            }\n            .disabled(!hasResponder)\n        }\n    }\n\n    func send(_ action: NSFindPanelAction) {\n        let item = NSMenuItem()\n        item.tag = Int(action.rawValue)\n        responder?.perform(Self.selector, with: item)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/WindowCommands/HelpCommands.swift",
    "content": "//\n//  HelpCommands.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 14/03/2023.\n//\n\nimport SwiftUI\n\nstruct HelpCommands: Commands {\n    var body: some Commands {\n        CommandGroup(after: .help) {\n            Button(\"What's New in CodeEdit\") {\n\n            }\n            .disabled(true)\n\n            Button(\"Release Notes\") {\n            }\n            .disabled(true)\n\n            Button(\"Report an Issue\") {\n                NSApp.sendAction(#selector(AppDelegate.openFeedback(_:)), to: nil, from: nil)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/WindowCommands/MainCommands.swift",
    "content": "//\n//  MainCommands.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 13/03/2023.\n//\n\nimport SwiftUI\nimport Sparkle\n\nstruct MainCommands: Commands {\n    @Environment(\\.openWindow)\n    var openWindow\n\n    var body: some Commands {\n        CommandGroup(replacing: .appInfo) {\n            Button(\"About CodeEdit\") {\n                openWindow(sceneID: .about)\n            }\n\n            Button(\"Check for updates...\") {\n                NSApp.sendAction(#selector(SPUStandardUpdaterController.checkForUpdates(_:)), to: nil, from: nil)\n            }\n        }\n\n        CommandGroup(replacing: .appSettings) {\n            Button(\"Settings...\") {\n                openWindow(sceneID: .settings)\n            }\n            .keyboardShortcut(\",\")\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/WindowCommands/NavigateCommands.swift",
    "content": "//\n//  NavigateCommands.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 13/03/2023.\n//\n\nimport SwiftUI\n\nstruct NavigateCommands: Commands {\n\n    @UpdatingWindowController var windowController: CodeEditWindowController?\n    private var editor: Editor? {\n        windowController?.workspace?.editorManager?.activeEditor\n    }\n\n    var body: some Commands {\n        CommandMenu(\"Navigate\") {\n            Group {\n                Button(\"Reveal in Project Navigator\") {\n                    NSApp.sendAction(#selector(ProjectNavigatorViewController.revealFile(_:)), to: nil, from: nil)\n                }\n                .keyboardShortcut(\"j\", modifiers: [.shift, .command])\n\n                Button(\"Reveal Changes in Navigator\") {\n\n                }\n                .keyboardShortcut(\"m\", modifiers: [.shift, .command])\n                .disabled(true)\n\n                Button(\"Open in Next Editor\") {\n\n                }\n                .keyboardShortcut(\",\", modifiers: [.option, .command])\n                .disabled(true)\n\n                Button(\"Open in...\") {\n\n                }\n                .disabled(true)\n\n                Divider()\n\n            }\n            Group {\n                Button(\"Show Previous Tab\") {\n                    editor?.selectPreviousTab()\n                }\n                .keyboardShortcut(\"{\", modifiers: [.command])\n                .disabled(editor?.tabs.count ?? 0 <= 1)  // Disable if there's one or no tabs\n\n                Button(\"Show Next Tab\") {\n                    editor?.selectNextTab()\n                }\n                .keyboardShortcut(\"}\", modifiers: [.command])\n                .disabled(editor?.tabs.count ?? 0 <= 1)  // Disable if there's one or no tabs\n            }\n            Group {\n                Divider()\n\n                Button(\"Go Forward\") {\n                    editor?.goForwardInHistory()\n                }\n                .disabled(!(editor?.canGoForwardInHistory ?? false))\n\n                Button(\"Go Back\") {\n                    editor?.goBackInHistory()\n                }\n                .disabled(!(editor?.canGoBackInHistory ?? false))\n            }\n            .disabled(editor == nil)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/WindowCommands/SourceControlCommands.swift",
    "content": "//\n//  SourceControlCommands.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 6/29/24.\n//\n\nimport SwiftUI\n\nstruct SourceControlCommands: Commands {\n    @State private var windowController: CodeEditWindowController?\n\n    @State private var confirmDiscardChanges: Bool = false\n\n    var sourceControlManager: SourceControlManager? {\n        windowController?.workspace?.sourceControlManager\n    }\n\n    var body: some Commands {\n        CommandMenu(\"Source Control\") {\n            Group {\n                Button(\"Commit...\") {\n                    // TODO: Open Source Control Navigator to Changes tab\n                }\n                .disabled(true)\n\n                Button(\"Push...\") {\n                    sourceControlManager?.pushSheetIsPresented = true\n                }\n\n                Button(\"Pull...\") {\n                    sourceControlManager?.pullSheetIsPresented = true\n                }\n                .keyboardShortcut(\"x\", modifiers: [.command, .option])\n\n                Button(\"Fetch Changes\") {\n                    sourceControlManager?.fetchSheetIsPresented = true\n                }\n\n                Divider()\n\n                Button(\"Stage All Changes\") {\n                    guard let sourceControlManager else { return }\n                    if sourceControlManager.changedFiles.isEmpty {\n                        sourceControlManager.noChangesToStageAlertIsPresented = true\n                    } else {\n                        Task {\n                            do {\n                                try await sourceControlManager.add(sourceControlManager.changedFiles.map { $0.fileURL })\n                            } catch {\n                                await sourceControlManager.showAlertForError(\n                                    title: \"Failed To Stage Changes\",\n                                    error: error\n                                )\n                            }\n                        }\n                    }\n                }\n\n                Button(\"Unstage All Changes\") {\n                    guard let sourceControlManager else { return }\n                    if sourceControlManager.changedFiles.isEmpty {\n                        sourceControlManager.noChangesToUnstageAlertIsPresented = true\n                    } else {\n                        Task {\n                            do {\n                                try await sourceControlManager.reset(\n                                    sourceControlManager.changedFiles.map { $0.fileURL }\n                                )\n                            } catch {\n                                await sourceControlManager.showAlertForError(\n                                    title: \"Failed To Unstage Changes\",\n                                    error: error\n                                )\n                            }\n                        }\n                    }\n                }\n\n                Divider()\n\n                Button(\"Cherry-Pick...\") {\n                    // TODO: Implementation Needed\n                }\n                .disabled(true)\n\n                Button(\"Stash Changes...\") {\n                    if sourceControlManager?.changedFiles.isEmpty ?? false {\n                        sourceControlManager?.noChangesToStashAlertIsPresented = true\n                    } else {\n                        sourceControlManager?.stashSheetIsPresented = true\n                    }\n                }\n\n                Divider()\n\n                Button(\"Discard All Changes...\") {\n                    if sourceControlManager?.changedFiles.isEmpty ?? false {\n                        sourceControlManager?.noChangesToDiscardAlertIsPresented = true\n                    } else {\n                        sourceControlManager?.discardAllAlertIsPresented = true\n                    }\n                }\n\n                Divider()\n\n                Button(\"Add Exisiting Remote...\") {\n                    sourceControlManager?.addExistingRemoteSheetIsPresented = true\n                }\n            }\n            .disabled(windowController?.workspace == nil)\n            .observeWindowController($windowController)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/WindowCommands/TasksCommands.swift",
    "content": "//\n//  TasksCommands.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/8/25.\n//\n\nimport SwiftUI\nimport Combine\n\nstruct TasksCommands: Commands {\n    @UpdatingWindowController var windowController: CodeEditWindowController?\n\n    var taskManager: TaskManager? {\n        windowController?.workspace?.taskManager\n    }\n\n    @State private var activeTaskStatus: CETaskStatus = .notRunning\n    @State private var taskManagerListener: AnyCancellable?\n    @State private var statusListener: AnyCancellable?\n\n    var body: some Commands {\n        CommandMenu(\"Tasks\") {\n            let selectedTaskName: String = if let selectedTask = taskManager?.selectedTask {\n                \"\\\"\" + selectedTask.name + \"\\\"\"\n            } else {\n                \"(No Selected Task)\"\n            }\n\n            Button(\"Run \\(selectedTaskName)\", systemImage: \"play.fill\") {\n                taskManager?.executeActiveTask()\n                showOutput()\n            }\n            .keyboardShortcut(\"R\")\n            .disabled(taskManager?.selectedTaskID == nil)\n\n            Button(\"Stop \\(selectedTaskName)\", systemImage: \"stop.fill\") {\n                taskManager?.terminateActiveTask()\n            }\n            .keyboardShortcut(\".\")\n            .onChange(of: windowController) { _, _ in\n                taskManagerListener = taskManager?.objectWillChange.sink {\n                    updateStatusListener()\n                }\n            }\n            .disabled(activeTaskStatus != .running)\n\n            Button(\"Show \\(selectedTaskName) Output\") {\n                showOutput()\n            }\n            // Disable when there's no output yet\n            .disabled(taskManager?.activeTasks[taskManager?.selectedTaskID ?? UUID()] == nil)\n\n            Divider()\n\n            Menu {\n                if let taskManager {\n                    ForEach(taskManager.availableTasks) { task in\n                        Button(task.name) {\n                            taskManager.selectedTaskID = task.id\n                        }\n                    }\n                }\n\n                if taskManager?.availableTasks.isEmpty ?? true {\n                    Button(\"Create Tasks\") {\n                        openSettings()\n                    }\n                }\n            } label: {\n                Text(\"Choose Task...\")\n            }\n            .disabled(taskManager?.availableTasks.isEmpty == true)\n\n            Button(\"Manage Tasks...\") {\n                openSettings()\n            }\n            .disabled(windowController == nil)\n        }\n    }\n\n    /// Update the ``statusListener`` to listen to a potentially new active task.\n    private func updateStatusListener() {\n        statusListener?.cancel()\n        guard let taskManager else { return }\n\n        activeTaskStatus = taskManager.activeTasks[taskManager.selectedTaskID ?? UUID()]?.status ?? .notRunning\n        guard let id = taskManager.selectedTaskID else { return }\n\n        statusListener = taskManager.activeTasks[id]?.$status.sink { newValue in\n            activeTaskStatus = newValue\n        }\n    }\n\n    private func showOutput() {\n        guard let utilityAreaModel = windowController?.workspace?.utilityAreaModel else {\n            return\n        }\n        if utilityAreaModel.isCollapsed {\n            // Open the utility area\n            utilityAreaModel.isCollapsed.toggle()\n        }\n        utilityAreaModel.selectedTab = .debugConsole // Switch to the correct tab\n        taskManager?.taskShowingOutput = taskManager?.selectedTaskID // Switch to the selected task\n    }\n\n    private func openSettings() {\n        NSApp.sendAction(\n            #selector(CodeEditWindowController.openWorkspaceSettings(_:)),\n            to: windowController,\n            from: nil\n        )\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/WindowCommands/Utils/CommandsFixes.swift",
    "content": "//\n//  CommandsFixes.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 11/03/2023.\n//\n\nimport SwiftUI\n\nextension EventModifiers {\n    static var hidden: EventModifiers = .numericPad\n}\n\nextension NSMenuItem {\n    @MainActor\n    @objc\n    fileprivate func fixAlternate(_ newValue: NSEvent.ModifierFlags) {\n        if newValue.contains(.numericPad) {\n            isAlternate = true\n            fixAlternate(newValue.subtracting(.numericPad))\n        }\n\n        fixAlternate(newValue)\n\n        if self.title == \"Open Recent\" {\n            self.submenu = FileCommands.recentProjectsMenu.makeMenu()\n        }\n\n        if self.title == \"OpenWindowAction\" || self.title.isEmpty {\n            self.isHidden = true\n            self.allowsKeyEquivalentWhenHidden = true\n        }\n    }\n\n    static func swizzle() {\n        let origSelector = #selector(setter: NSMenuItem.keyEquivalentModifierMask)\n        let swizzledSelector = #selector(fixAlternate)\n        let originalMethodSet = class_getInstanceMethod(self as AnyClass, origSelector)\n        let swizzledMethodSet = class_getInstanceMethod(self as AnyClass, swizzledSelector)\n\n        method_exchangeImplementations(originalMethodSet!, swizzledMethodSet!)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/WindowCommands/Utils/FirstResponderPropertyWrapper.swift",
    "content": "//\n//  FirstResponderPropertyWrapper.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 14/03/2023.\n//\n\nimport SwiftUI\n\n/// A property wrapper which allows for easy access to the current first responder.\n/// This differs from the SwiftUI Focus System, as you get AppKit NSResponders, which you can call methods on.\n/// It can also be easily checked if the current first selector accepts some event.\n@propertyWrapper\nstruct FirstResponder: DynamicProperty {\n    @StateObject var helper = HelperClass()\n\n    var wrappedValue: NSResponder? {\n        helper.responder\n    }\n\n    class HelperClass: ObservableObject {\n        @Published var responder: NSResponder? = NSApp.keyWindow?.firstResponder\n\n        init() {\n            NSApp.publisher(for: \\.keyWindow?.firstResponder).assign(to: &$responder)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/WindowCommands/Utils/KeyWindowControllerObserver.swift",
    "content": "//\n//  KeyWindowControllerObserver.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/2/24.\n//\n\nimport SwiftUI\n\nstruct KeyWindowControllerObserver: ViewModifier {\n    @Binding var windowController: CodeEditWindowController?\n\n    func body(content: Content) -> some View {\n        content.onReceive(NSApp.publisher(for: \\.keyWindow)) { window in\n            windowController = window?.windowController as? CodeEditWindowController\n        }\n    }\n}\n\nextension View {\n    @ViewBuilder\n    func observeWindowController(_ binding: Binding<CodeEditWindowController?>) -> some View {\n        self.modifier(KeyWindowControllerObserver(windowController: binding))\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/WindowCommands/Utils/RecentProjectsMenu.swift",
    "content": "//\n//  RecentProjectsMenu.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 10/22/24.\n//\n\nimport AppKit\nimport WelcomeWindow\n\n@MainActor\nfinal class RecentProjectsMenu: NSObject, NSMenuDelegate {\n\n    // MARK: - Menu construction\n\n    private let menuTitle = NSLocalizedString(\n        \"Open Recent\",\n        comment: \"Open Recent menu title\"\n    )\n\n    private lazy var menu: NSMenu = {\n        let menu = NSMenu(title: menuTitle)\n        menu.delegate = self           // <- make the menu ask us for updates\n        return menu\n    }()\n\n    /// Entry point used by the caller (e.g. the main menu bar template).\n    func makeMenu() -> NSMenu {\n        rebuildMenu()\n        return menu\n    }\n\n    /// Called automatically right before the menu gets displayed.\n    func menuNeedsUpdate(_ menu: NSMenu) {\n        rebuildMenu()\n    }\n\n    // Rebuilds the whole “Open Recent” menu.\n    private func rebuildMenu() {\n        menu.removeAllItems()\n\n        addFileURLs(\n            to: menu,\n            fileURLs: RecentsStore.recentDirectoryURLs().prefix(10)\n        )\n        menu.addItem(.separator())\n        addFileURLs(\n            to: menu,\n            fileURLs: RecentsStore.recentFileURLs().prefix(10)\n        )\n        menu.addItem(.separator())\n\n        let clearMenuItem = NSMenuItem(\n            title: NSLocalizedString(\n                \"Clear Menu\",\n                comment: \"Recent project menu clear button\"\n            ),\n            action: #selector(clearMenuItemClicked(_:)),\n            keyEquivalent: \"\"\n        )\n        clearMenuItem.target = self\n        menu.addItem(clearMenuItem)\n    }\n\n    // MARK: - Item creation helpers\n\n    private func addFileURLs(to menu: NSMenu, fileURLs: ArraySlice<URL>) {\n        for url in fileURLs {\n            let icon = NSWorkspace.shared.icon(forFile: url.path(percentEncoded: false))\n            icon.size = NSSize(width: 16, height: 16)\n            let alternateTitle = alternateTitle(for: url)\n\n            let primaryItem = NSMenuItem(\n                title: url.lastPathComponent,\n                action: #selector(recentProjectItemClicked(_:)),\n                keyEquivalent: \"\"\n            )\n            primaryItem.target = self\n            primaryItem.image = icon\n            primaryItem.representedObject = url\n\n            let containsDuplicate = fileURLs.contains { otherURL in\n                url != otherURL && url.lastPathComponent == otherURL.lastPathComponent\n            }\n\n            // If there's a duplicate, add the path.\n            if containsDuplicate {\n                primaryItem.attributedTitle = alternateTitle\n            }\n\n            let alternateItem = NSMenuItem(\n                title: \"\",\n                action: #selector(recentProjectItemClicked(_:)),\n                keyEquivalent: \"\"\n            )\n            alternateItem.attributedTitle = alternateTitle\n            alternateItem.target = self\n            alternateItem.image = icon\n            alternateItem.representedObject = url\n            alternateItem.isAlternate = true\n            alternateItem.keyEquivalentModifierMask = [.option]\n\n            menu.addItem(primaryItem)\n            menu.addItem(alternateItem)\n        }\n    }\n\n    private func alternateTitle(for projectPath: URL) -> NSAttributedString {\n        let parentPath = projectPath\n            .deletingLastPathComponent()\n            .path(percentEncoded: false)\n            .abbreviatingWithTildeInPath()\n        let alternateTitle = NSMutableAttributedString(\n            string: projectPath.lastPathComponent + \" \",\n            attributes: [.foregroundColor: NSColor.labelColor]\n        )\n        alternateTitle.append(NSAttributedString(\n            string: parentPath,\n            attributes: [.foregroundColor: NSColor.secondaryLabelColor]\n        ))\n        return alternateTitle\n    }\n\n    // MARK: - Actions\n\n    @objc\n    private func recentProjectItemClicked(_ sender: NSMenuItem) {\n        guard let projectURL = sender.representedObject as? URL else { return }\n        CodeEditDocumentController.shared.openDocument(\n            withContentsOf: projectURL,\n            display: true,\n            completionHandler: { _, _, _ in }\n        )\n    }\n\n    @objc\n    private func clearMenuItemClicked(_ sender: NSMenuItem) {\n        RecentsStore.clearList()\n        rebuildMenu()\n    }\n}\n\n// MARK: - Helpers\n\nprivate extension String {\n    func abbreviatingWithTildeInPath() -> String {\n        (self as NSString).abbreviatingWithTildeInPath\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/WindowCommands/Utils/WindowControllerPropertyWrapper.swift",
    "content": "//\n//  WindowControllerPropertyWrapper.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/14/24.\n//\n\nimport AppKit\nimport SwiftUI\nimport Combine\n\n/// Provides an auto-updating reference to ``CodeEditWindowController``. The value will update as the key window\n/// changes, and does not keep a strong reference to the controller.\n///\n/// Sample usage:\n/// ```swift\n/// struct WindowCommands: Commands {\n///     @UpdatingWindowController var windowController\n///\n///     var body: some Commands {\n///         Button(\"Button that needs the window\") {\n///             print(\"Window exists\")\n///         }\n///         .disabled(windowController == nil)\n///     }\n/// }\n/// ```\n@propertyWrapper\nstruct UpdatingWindowController: DynamicProperty {\n    @StateObject var box = WindowControllerBox()\n\n    var wrappedValue: CodeEditWindowController? {\n        box.controller\n    }\n\n    class WindowControllerBox: ObservableObject {\n        public private(set) weak var controller: CodeEditWindowController?\n\n        private var windowCancellable: AnyCancellable? // Needs to stick around between window changes.\n        private var cancellables: Set<AnyCancellable> = []\n\n        init() {\n            windowCancellable = NSApp.publisher(for: \\.keyWindow).receive(on: RunLoop.main).sink { [weak self] window in\n                // Fix an issue where NSMenuItems with custom views would trigger this callback.\n                guard window?.className != \"NSPopupMenuWindow\" else { return }\n                self?.setNewController(window?.windowController as? CodeEditWindowController)\n            }\n        }\n\n        func setNewController(_ controller: CodeEditWindowController?) {\n            cancellables.forEach { $0.cancel() }\n            cancellables.removeAll()\n\n            self.controller = controller\n\n            controller?.objectWillChange.sink { [weak self] in\n                self?.objectWillChange.send()\n            }\n            .store(in: &cancellables)\n\n            controller?.workspace?.utilityAreaModel?.objectWillChange.sink { [weak self] in\n                self?.objectWillChange.send()\n            }\n            .store(in: &cancellables)\n\n            let activeEditor = controller?.workspace?.editorManager?.activeEditor\n            activeEditor?.objectWillChange.sink { [weak self] in\n                self?.objectWillChange.send()\n            }\n            .store(in: &cancellables)\n\n            controller?.workspace?.taskManager?.objectWillChange.sink { [weak self] in\n                self?.objectWillChange.send()\n            }\n            .store(in: &cancellables)\n\n            self.objectWillChange.send()\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/WindowCommands/ViewCommands.swift",
    "content": "//\n//  ViewCommands.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 13/03/2023.\n//\n\nimport SwiftUI\nimport Combine\n\nstruct ViewCommands: Commands {\n    @AppSettings(\\.textEditing.font.size)\n    var editorFontSize\n    @AppSettings(\\.terminal.font.size)\n    var terminalFontSize\n    @AppSettings(\\.general.showEditorJumpBar)\n    var showEditorJumpBar\n    @AppSettings(\\.general.dimEditorsWithoutFocus)\n    var dimEditorsWithoutFocus\n\n    @FocusedBinding(\\.navigationSplitViewVisibility)\n    var navigationSplitViewVisibility\n\n    @FocusedBinding(\\.inspectorVisibility)\n    var inspectorVisibility\n\n    @UpdatingWindowController var windowController: CodeEditWindowController?\n\n    var body: some Commands {\n        CommandGroup(after: .toolbar) {\n            Button(\"Show Command Palette\") {\n                NSApp.sendAction(#selector(CodeEditWindowController.openCommandPalette(_:)), to: nil, from: nil)\n            }\n            .keyboardShortcut(\"p\", modifiers: [.shift, .command])\n\n            Button(\"Open Search Navigator\") {\n                NSApp.sendAction(#selector(CodeEditWindowController.openSearchNavigator(_:)), to: nil, from: nil)\n            }\n            .keyboardShortcut(\"f\", modifiers: [.shift, .command])\n\n            Menu(\"Font Size\") {\n                Button(\"Increase\") {\n                    if editorFontSize < 288 {\n                        editorFontSize += 1\n                    }\n                    if terminalFontSize < 288 {\n                        terminalFontSize += 1\n                    }\n                }\n                .keyboardShortcut(\"+\")\n\n                Button(\"Decrease\") {\n                    if editorFontSize > 1 {\n                        editorFontSize -= 1\n                    }\n                    if terminalFontSize > 1 {\n                        terminalFontSize -= 1\n                    }\n                }\n                .keyboardShortcut(\"-\")\n\n                Divider()\n\n                Button(\"Reset\") {\n                    editorFontSize = 12\n                    terminalFontSize = 12\n                }\n                .keyboardShortcut(\"0\", modifiers: [.command, .control])\n            }\n            .disabled(windowController == nil)\n\n            Button(\"Customize Toolbar...\") {\n\n            }\n            .disabled(true)\n\n            Divider()\n\n            HideCommands()\n\n            Divider()\n\n            Button(\"\\(showEditorJumpBar ? \"Hide\" : \"Show\") Jump Bar\") {\n                showEditorJumpBar.toggle()\n            }\n\n            Toggle(\"Dim editors without focus\", isOn: $dimEditorsWithoutFocus)\n\n            Divider()\n\n            if let model = windowController?.navigatorSidebarViewModel {\n                Divider()\n                NavigatorCommands(model: model)\n            }\n        }\n    }\n}\n\nextension ViewCommands {\n    struct HideCommands: View {\n        @UpdatingWindowController var windowController: CodeEditWindowController?\n\n        var navigatorCollapsed: Bool {\n            windowController?.navigatorCollapsed ?? true\n        }\n\n        var inspectorCollapsed: Bool {\n            windowController?.inspectorCollapsed ?? true\n        }\n\n        var utilityAreaCollapsed: Bool {\n            windowController?.workspace?.utilityAreaModel?.isCollapsed ?? true\n        }\n\n        var toolbarCollapsed: Bool {\n            windowController?.toolbarCollapsed ?? true\n        }\n\n        var isInterfaceHidden: Bool {\n            return windowController?.isInterfaceStillHidden() ?? false\n        }\n\n        var body: some View {\n            Button(\"\\(navigatorCollapsed ? \"Show\" : \"Hide\") Navigator\") {\n                windowController?.toggleFirstPanel()\n            }\n            .disabled(windowController == nil)\n            .keyboardShortcut(\"0\", modifiers: [.command])\n\n            Button(\"\\(inspectorCollapsed ? \"Show\" : \"Hide\") Inspector\") {\n                windowController?.toggleLastPanel()\n            }\n            .disabled(windowController == nil)\n            .keyboardShortcut(\"i\", modifiers: [.control, .command])\n\n            Button(\"\\(utilityAreaCollapsed ? \"Show\" : \"Hide\") Utility Area\") {\n                CommandManager.shared.executeCommand(\"open.drawer\")\n            }\n            .disabled(windowController == nil)\n            .keyboardShortcut(\"y\", modifiers: [.shift, .command])\n\n            Button(\"\\(toolbarCollapsed ? \"Show\" : \"Hide\") Toolbar\") {\n                windowController?.toggleToolbar()\n            }\n            .disabled(windowController == nil)\n            .keyboardShortcut(\"t\", modifiers: [.option, .command])\n\n            Button(\"\\(isInterfaceHidden ? \"Show\" : \"Hide\") Interface\") {\n                windowController?.toggleInterface(shouldHide: !isInterfaceHidden)\n            }\n            .disabled(windowController == nil)\n            .keyboardShortcut(\"H\", modifiers: [.shift, .command])\n        }\n    }\n}\n\nextension ViewCommands {\n    struct NavigatorCommands: View {\n        @ObservedObject var model: NavigatorAreaViewModel\n\n        var body: some View {\n            Menu(\"Navigators\", content: {\n                ForEach(Array(model.tabItems.prefix(9).enumerated()), id: \\.element) { index, tab in\n                    Button(tab.title) {\n                        model.setNavigatorTab(tab: tab)\n                    }\n                    .keyboardShortcut(KeyEquivalent(Character(String(index + 1))))\n                }\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Features/WindowCommands/WindowCommands.swift",
    "content": "//\n//  WindowCommands.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 13/03/2023.\n//\n\nimport SwiftUI\n\nstruct WindowCommands: Commands {\n    @Environment(\\.openWindow)\n    var openWindow\n\n    var body: some Commands {\n        CommandGroup(replacing: .singleWindowList) {\n            Button(\"Welcome to CodeEdit\") {\n                openWindow(sceneID: .welcome)\n            }\n            .keyboardShortcut(\"1\", modifiers: [.shift, .command])\n\n            Button(\"About CodeEdit\") {\n                openWindow(sceneID: .about)\n            }\n            .keyboardShortcut(\"2\", modifiers: [.shift, .command])\n\n            Button(\"Manage Extensions\") {\n                openWindow(sceneID: .extensions)\n            }\n            .keyboardShortcut(\"3\", modifiers: [.shift, .command])\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CE_VERSION_POSTFIX</key>\n\t<string>${CE_VERSION_POSTFIX}</string>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>$(DEVELOPMENT_LANGUAGE)</string>\n\t<key>CFBundleDocumentTypes</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>h</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>C header file</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>c</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>C source code</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>gitattributes</string>\n\t\t\t\t<string>gitconfig</string>\n\t\t\t\t<string>gitignore</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Git configuration file</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>asp</string>\n\t\t\t\t<string>aspx</string>\n\t\t\t\t<string>cshtml</string>\n\t\t\t\t<string>jshtm</string>\n\t\t\t\t<string>jsp</string>\n\t\t\t\t<string>phtml</string>\n\t\t\t\t<string>shtml</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>HTML template document</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>bat</string>\n\t\t\t\t<string>cmd</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Windows command script</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>bowerrc</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Bower document</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>config</string>\n\t\t\t\t<string>editorconfig</string>\n\t\t\t\t<string>ini</string>\n\t\t\t\t<string>cfg</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Configuration file</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>hh</string>\n\t\t\t\t<string>hpp</string>\n\t\t\t\t<string>hxx</string>\n\t\t\t\t<string>h++</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>C++ header file</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>cc</string>\n\t\t\t\t<string>cpp</string>\n\t\t\t\t<string>cxx</string>\n\t\t\t\t<string>c++</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>C++ source code</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>m</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Objective-C source code</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>mm</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Objective-C++ source code</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>Swift</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Swift source code</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>cs</string>\n\t\t\t\t<string>csx</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>C# source code</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>css</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>CSS</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>go</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Go source code</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>htm</string>\n\t\t\t\t<string>html</string>\n\t\t\t\t<string>xhtml</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>HTML document</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>jade</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Jade document</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>jav</string>\n\t\t\t\t<string>java</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Java document</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>js</string>\n\t\t\t\t<string>jscsrc</string>\n\t\t\t\t<string>jshintrc</string>\n\t\t\t\t<string>mjs</string>\n\t\t\t\t<string>cjs</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Javascript file</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>json</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>JSON document</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>less</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Less document</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>markdown</string>\n\t\t\t\t<string>md</string>\n\t\t\t\t<string>mdoc</string>\n\t\t\t\t<string>mdown</string>\n\t\t\t\t<string>mdtext</string>\n\t\t\t\t<string>mdtxt</string>\n\t\t\t\t<string>mdwn</string>\n\t\t\t\t<string>mkd</string>\n\t\t\t\t<string>mkdn</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Markdown document</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>php</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>PHP source code</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>ps1</string>\n\t\t\t\t<string>psd1</string>\n\t\t\t\t<string>psm1</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Powershell script</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>py</string>\n\t\t\t\t<string>pyi</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Python script</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>gemspec</string>\n\t\t\t\t<string>rb</string>\n\t\t\t\t<string>erb</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Ruby source code</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>scss</string>\n\t\t\t\t<string>sass</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>SASS file</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>sql</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>SQL script</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>ts</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>TypeScript file</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>tsx</string>\n\t\t\t\t<string>jsx</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>React source code</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>vue</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Vue source code</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>ascx</string>\n\t\t\t\t<string>csproj</string>\n\t\t\t\t<string>dtd</string>\n\t\t\t\t<string>plist</string>\n\t\t\t\t<string>wxi</string>\n\t\t\t\t<string>wxl</string>\n\t\t\t\t<string>wxs</string>\n\t\t\t\t<string>xml</string>\n\t\t\t\t<string>xaml</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>XML document</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>eyaml</string>\n\t\t\t\t<string>eyml</string>\n\t\t\t\t<string>yaml</string>\n\t\t\t\t<string>yml</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>YAML document</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>bash</string>\n\t\t\t\t<string>bash_login</string>\n\t\t\t\t<string>bash_logout</string>\n\t\t\t\t<string>bash_profile</string>\n\t\t\t\t<string>bashrc</string>\n\t\t\t\t<string>profile</string>\n\t\t\t\t<string>rhistory</string>\n\t\t\t\t<string>rprofile</string>\n\t\t\t\t<string>sh</string>\n\t\t\t\t<string>zlogin</string>\n\t\t\t\t<string>zlogout</string>\n\t\t\t\t<string>zprofile</string>\n\t\t\t\t<string>zsh</string>\n\t\t\t\t<string>zshenv</string>\n\t\t\t\t<string>zshrc</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Shell script</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>clj</string>\n\t\t\t\t<string>cljs</string>\n\t\t\t\t<string>cljx</string>\n\t\t\t\t<string>clojure</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Clojure source code</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>coffee</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>CoffeeScript source code</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>csv</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Comma Separated Values</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>cmake</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>CMake script</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>dart</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Dart script</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>diff</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Diff file</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>dockerfile</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Dockerfile</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>gradle</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Gradle file</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>groovy</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Groovy script</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>makefile</string>\n\t\t\t\t<string>mk</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Makefile</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>lua</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Lua script</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>pug</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Pug document</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>ipynb</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Jupyter</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>lock</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Lockfile</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>log</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Log file</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>txt</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Plain Text File</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>vb</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Visual Basic script</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>r</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>R source code</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>rs</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Rust source code</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>rst</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Restructured Text document</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>tex</string>\n\t\t\t\t<string>cls</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>LaTeX document</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>fs</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>F# source code</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>fsi</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>F# signature file</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>fsx</string>\n\t\t\t\t<string>fsscript</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>F# script</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>svg</string>\n\t\t\t\t<string>svgz</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>SVG document</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>toml</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>TOML document</string>\n\t\t\t<key>CFBundleTypeOSTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>TEXT</string>\n\t\t\t\t<string>utxt</string>\n\t\t\t\t<string>TUTX</string>\n\t\t\t\t<string>****</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Source Code</string>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>LSHandlerRank</key>\n\t\t\t<string>Default</string>\n\t\t\t<key>LSItemContentTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>public.source-code</string>\n\t\t\t\t<string>public.item</string>\n\t\t\t</array>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>CodeFileDocument</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Workspace Folder</string>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Viewer</string>\n\t\t\t<key>LSHandlerRank</key>\n\t\t\t<string>Default</string>\n\t\t\t<key>LSItemContentTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>public.folder</string>\n\t\t\t</array>\n\t\t\t<key>NSDocumentClass</key>\n\t\t\t<string>WorkspaceDocument</string>\n\t\t</dict>\n\t</array>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>0.3.6</string>\n\t<key>CFBundleURLTypes</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>CFBundleURLSchemes</key>\n\t\t\t<array>\n\t\t\t\t<string>codeedit</string>\n\t\t\t</array>\n\t\t</dict>\n\t</array>\n\t<key>CFBundleVersion</key>\n\t<string>47</string>\n\t<key>LSApplicationCategoryType</key>\n\t<string>public.app-category.developer-tools</string>\n\t<key>NSHumanReadableCopyright</key>\n\t<string>${CE_COPYRIGHT}</string>\n\t<key>SUEnableInstallerLauncherService</key>\n\t<true/>\n\t<key>SUEnableJavaScript</key>\n\t<true/>\n\t<key>SUFeedURL</key>\n\t<string>https://github.com/CodeEditApp/CodeEdit/releases/latest/download/appcast.xml</string>\n\t<key>SUPublicEDKey</key>\n\t<string>/vAnxnK9wj4IqnUt6wS9EN3Ug69zHb+S/Pb9CyZuwa0=</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "CodeEdit/Localization/Localized+Ex.swift",
    "content": "import SwiftUI\n\nextension String {\n    func localized(_ custom: String? = nil) -> LocalizedStringKey {\n        if let custom {\n            return LocalizedStringKey(custom)\n        } else {\n            return LocalizedStringKey(self)\n        }\n    }\n}\n\nextension LocalizedStringKey {\n    static let helloWorld = \"Hello, world!\".localized()\n}\n"
  },
  {
    "path": "CodeEdit/Localization/en.lproj/Localizable.strings",
    "content": "\"Hello, world!\"=\"Hello, world!\";\n\n// Settings - General Tab\n\"General\"=\"General\";\n\n// Settings - File Icon Style\n\"File Icon Style\"=\"File Icon Style\";\n\"Color\"=\"Color\";\n\"Monochrome\"=\"Monochrome\";\n\n// Settings - Appearance\n\"Appearance\"=\"Appearance\";\n\"System\"=\"System\";\n\"Light\"=\"Light\";\n\"Dark\"=\"Dark\";\n\n// Settings - Reopen Behavior\n\"Reopen Behavior\"=\"Reopen Behavior\";\n\"Welcome Screen\"=\"Welcome Screen\";\n\"Open Panel\"=\"Open Panel\";\n\"New Document\"=\"New Document\";\n\"Remove from Recent Projects\"=\"Remove from Recent Projects\";\n\"Show in Finder\"=\"Show in Finder\";\n\"Copy Path\"=\"Copy Path\";\n\n// Settings - Editor Theme\n\"Editor Theme\"=\"Editor Theme\";\n\n// Settings - Default Tab Width\n\"Default Tab Width\"=\"Default Tab Width\";\n\n// Settings - Terminal Tab\n\"Terminal\"=\"Terminal\";\n\n// Settings - Terminal Shell Type\n\"Terminal Shell\"=\"Terminal Shell\";\n\"System Default\"=\"System Default\";\n\n// Settings - Terminal Font\n\"Terminal Font\"=\"Terminal Font\";\n\"System Font\"=\"System Font\";\n\"Custom\"=\"Custom\";\n\n//Editor Screen\n\"Open file from sidebar\"=\"Open file from sidebar\";\n\"CodeEdit cannot open this file because its file type is not supported.\" = \"CodeEdit cannot open this file because its file type is not supported.\";\n\n// Inspector Sidebar\n\"File Inspector\"=\"File Inspector\";\n\"History Inspector\"=\"History Inspector\";\n\"Quick Help Inspector\"=\"Quick Help Inspector\";\n\n// Project Navigator Item\n\"Add File\"=\"Add File\";\n\"Not implemented yet\"=\"Not implemented yet\";\n\n// Leading Sidebar Toolbar\n\"Alphabetically\"=\"Alphabetically\";\n\"Folders on top\"=\"Folders on top\";\n\n\n// Welcome Screen\n\"Welcome to CodeEdit\" = \"Welcome to CodeEdit\";\n\"Version %@ (%@)\" = \"Version %@ (%@)\";\n\"No Recent Projects\" = \"No Recent Projects\";\n\n// Welcome Screen - New File\n\"Create a new file\" = \"Create a new file\";\n\"Clone an existing project\" = \"Clone an existing project\";\n\n// Welcome Screen - Open File\n\"Open a file or folder\" = \"Open a file or folder\";\n\"Open an existing file or folder on your Mac\" = \"Open an existing file or folder on your Mac\";\n\n// Welcome Screen - SCM\n\"Start working on something from a Git repository\" = \"Start working on something from a Git repository\";\n\n\"Remove from Recent Projects\" = \"Remove from Recent Projects\";\n\"Show in Finder\" = \"Show in Finder\";\n\"Copy Path\" = \"Copy Path\";\n"
  },
  {
    "path": "CodeEdit/Preview Content/Preview Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "CodeEdit/SceneID.swift",
    "content": "//\n//  SceneID.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 15/03/2023.\n//\n\nimport Foundation\n\nenum SceneID: String, CaseIterable {\n    case welcome\n    case about\n    case extensions\n    case settings\n}\n"
  },
  {
    "path": "CodeEdit/ShellIntegration/codeedit_shell_integration.bash",
    "content": "#  codeedit_shell_intergration.bash\n#  CodeEdit\n#\n#  Created by Qian Qian \"Cubik\" (@Cubik65536) on 2023-06-13.\n#\n#  This script is used to configure bash shells\n#  so the terminal title can be set properly\n#  with shell name or program's command name\n#\n#  bash-preexec.sh (https://github.com/rcaloras/bash-preexec, licensed under MIT)\n#  is used so we can use ZSH-like 'preexec' and 'precmd' functions\n#\n#  Parts of this file also use modified versions of a source file from Microsoft's VSCode. MIT License.\n#  Permalink to original, licensed file:\n#  https://github.com/microsoft/vscode/blob/60d7343892f10e0c5f09cb55a6a3f268eb0dd4fb/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh\n\n# BEGIN: Modified Microsoft code\n\n# Prevent the script recursing when setting up\nif [[ -n \"${CE_SHELL_INTEGRATION}\" ]]; then\n\tbuiltin return\nfi\n\nCE_SHELL_INTEGRATION=1\n\n# Run relevant rc/profile only if shell integration has been injected, not when run manually\nif [ \"$CE_INJECTION\" == \"1\" ]; then\n\tif [ -z \"$CE_SHELL_LOGIN\" ]; then\n\t\tif [ -r ~/.bashrc ]; then\n\t\t\t. ~/.bashrc\n\t\tfi\n\telse\n\t\t# Imitate -l because --init-file doesn't support it:\n\t\t# run the first of these files that exists\n\t\tif [ -r /etc/profile ]; then\n\t\t\t. /etc/profile\n\t\tfi\n\t\t# execute the first that exists\n\t\tif [ -r ~/.bash_profile ]; then\n\t\t\t. ~/.bash_profile\n\t\telif [ -r ~/.bash_login ]; then\n\t\t\t. ~/.bash_login\n\t\telif [ -r ~/.profile ]; then\n\t\t\t. ~/.profile\n\t\tfi\n\t\tbuiltin unset CE_SHELL_LOGIN\n\tfi\n\tbuiltin unset CE_INJECTION\nfi\n\n# END: Modified Microsoft code\n\n# Wrap bash-preexec.sh in a function so that, if it exits early due to having\n# been sourced elsewhere, it doesn't exit our entire script.\n_install_bash_preexec () {\n\n# -- BEGIN BASH-PREEXEC.SH --\n# bash-preexec.sh -- Bash support for ZSH-like 'preexec' and 'precmd' functions.\n# https://github.com/rcaloras/bash-preexec\n#\n#\n# 'preexec' functions are executed before each interactive command is\n# executed, with the interactive command as its argument. The 'precmd'\n# function is executed before each prompt is displayed.\n#\n# Author: Ryan Caloras (ryan@bashhub.com)\n# Forked from Original Author: Glyph Lefkowitz\n#\n# V0.5.0\n#\n\n# General Usage:\n#\n#  1. Source this file at the end of your bash profile so as not to interfere\n#     with anything else that's using PROMPT_COMMAND.\n#\n#  2. Add any precmd or preexec functions by appending them to their arrays:\n#       e.g.\n#       precmd_functions+=(my_precmd_function)\n#       precmd_functions+=(some_other_precmd_function)\n#\n#       preexec_functions+=(my_preexec_function)\n#\n#  3. Consider changing anything using the DEBUG trap or PROMPT_COMMAND\n#     to use preexec and precmd instead. Preexisting usages will be\n#     preserved, but doing so manually may be less surprising.\n#\n#  Note: This module requires two Bash features which you must not otherwise be\n#  using: the \"DEBUG\" trap, and the \"PROMPT_COMMAND\" variable. If you override\n#  either of these after bash-preexec has been installed it will most likely break.\n\n# Tell shellcheck what kind of file this is.\n# shellcheck shell=bash\n\n# Make sure this is bash that's running and return otherwise.\n# Use POSIX syntax for this line:\nif [ -z \"${BASH_VERSION-}\" ]; then\n    return 1;\nfi\n\n# We only support Bash 3.1+.\n# Note: BASH_VERSINFO is first available in Bash-2.0.\nif [[ -z \"${BASH_VERSINFO-}\" ]] || (( BASH_VERSINFO[0] < 3 || (BASH_VERSINFO[0] == 3 && BASH_VERSINFO[1] < 1) )); then\n    return 1\nfi\n\n# Avoid duplicate inclusion\nif [[ -n \"${bash_preexec_imported:-}\" ]]; then\n    return 0\nfi\nbash_preexec_imported=\"defined\"\n\n# WARNING: This variable is no longer used and should not be relied upon.\n# Use ${bash_preexec_imported} instead.\n# shellcheck disable=SC2034\n__bp_imported=\"${bash_preexec_imported}\"\n\n# Should be available to each precmd and preexec\n# functions, should they want it. $? and $_ are available as $? and $_, but\n# $PIPESTATUS is available only in a copy, $BP_PIPESTATUS.\n# TODO: Figure out how to restore PIPESTATUS before each precmd or preexec\n# function.\n__bp_last_ret_value=\"$?\"\nBP_PIPESTATUS=(\"${PIPESTATUS[@]}\")\n__bp_last_argument_prev_command=\"$_\"\n\n__bp_inside_precmd=0\n__bp_inside_preexec=0\n\n# Initial PROMPT_COMMAND string that is removed from PROMPT_COMMAND post __bp_install\n__bp_install_string=$'__bp_trap_string=\"$(trap -p DEBUG)\"\\ntrap - DEBUG\\n__bp_install'\n\n# Fails if any of the given variables are readonly\n# Reference https://stackoverflow.com/a/4441178\n__bp_require_not_readonly() {\n  local var\n  for var; do\n    if ! ( unset \"$var\" 2> /dev/null ); then\n      echo \"bash-preexec requires write access to ${var}\" >&2\n      return 1\n    fi\n  done\n}\n\n# Remove ignorespace and or replace ignoreboth from HISTCONTROL\n# so we can accurately invoke preexec with a command from our\n# history even if it starts with a space.\n__bp_adjust_histcontrol() {\n    local histcontrol\n    histcontrol=\"${HISTCONTROL:-}\"\n    histcontrol=\"${histcontrol//ignorespace}\"\n    # Replace ignoreboth with ignoredups\n    if [[ \"$histcontrol\" == *\"ignoreboth\"* ]]; then\n        histcontrol=\"ignoredups:${histcontrol//ignoreboth}\"\n    fi;\n    export HISTCONTROL=\"$histcontrol\"\n}\n\n# This variable describes whether we are currently in \"interactive mode\";\n# i.e. whether this shell has just executed a prompt and is waiting for user\n# input.  It documents whether the current command invoked by the trace hook is\n# run interactively by the user; it's set immediately after the prompt hook,\n# and unset as soon as the trace hook is run.\n__bp_preexec_interactive_mode=\"\"\n\n# These arrays are used to add functions to be run before, or after, prompts.\ndeclare -a precmd_functions\ndeclare -a preexec_functions\n\n# Trims leading and trailing whitespace from $2 and writes it to the variable\n# name passed as $1\n__bp_trim_whitespace() {\n    local var=${1:?} text=${2:-}\n    text=\"${text#\"${text%%[![:space:]]*}\"}\"   # remove leading whitespace characters\n    text=\"${text%\"${text##*[![:space:]]}\"}\"   # remove trailing whitespace characters\n    printf -v \"$var\" '%s' \"$text\"\n}\n\n\n# Trims whitespace and removes any leading or trailing semicolons from $2 and\n# writes the resulting string to the variable name passed as $1. Used for\n# manipulating substrings in PROMPT_COMMAND\n__bp_sanitize_string() {\n    local var=${1:?} text=${2:-} sanitized\n    __bp_trim_whitespace sanitized \"$text\"\n    sanitized=${sanitized%;}\n    sanitized=${sanitized#;}\n    __bp_trim_whitespace sanitized \"$sanitized\"\n    printf -v \"$var\" '%s' \"$sanitized\"\n}\n\n# This function is installed as part of the PROMPT_COMMAND;\n# It sets a variable to indicate that the prompt was just displayed,\n# to allow the DEBUG trap to know that the next command is likely interactive.\n__bp_interactive_mode() {\n    __bp_preexec_interactive_mode=\"on\";\n}\n\n\n# This function is installed as part of the PROMPT_COMMAND.\n# It will invoke any functions defined in the precmd_functions array.\n__bp_precmd_invoke_cmd() {\n    # Save the returned value from our last command, and from each process in\n    # its pipeline. Note: this MUST be the first thing done in this function.\n    # BP_PIPESTATUS may be unused, ignore\n    # shellcheck disable=SC2034\n\n    __bp_last_ret_value=\"$?\" BP_PIPESTATUS=(\"${PIPESTATUS[@]}\")\n\n    # Don't invoke precmds if we are inside an execution of an \"original\n    # prompt command\" by another precmd execution loop. This avoids infinite\n    # recursion.\n    if (( __bp_inside_precmd > 0 )); then\n      return\n    fi\n    local __bp_inside_precmd=1\n\n    # Invoke every function defined in our function array.\n    local precmd_function\n    for precmd_function in \"${precmd_functions[@]}\"; do\n\n        # Only execute this function if it actually exists.\n        # Test existence of functions with: declare -[Ff]\n        if type -t \"$precmd_function\" 1>/dev/null; then\n            __bp_set_ret_value \"$__bp_last_ret_value\" \"$__bp_last_argument_prev_command\"\n            # Quote our function invocation to prevent issues with IFS\n            \"$precmd_function\"\n        fi\n    done\n\n    __bp_set_ret_value \"$__bp_last_ret_value\"\n}\n\n# Sets a return value in $?. We may want to get access to the $? variable in our\n# precmd functions. This is available for instance in zsh. We can simulate it in bash\n# by setting the value here.\n__bp_set_ret_value() {\n    return ${1:+\"$1\"}\n}\n\n__bp_in_prompt_command() {\n\n    local prompt_command_array IFS=$'\\n;'\n    read -rd '' -a prompt_command_array <<< \"${PROMPT_COMMAND[*]:-}\"\n\n    local trimmed_arg\n    __bp_trim_whitespace trimmed_arg \"${1:-}\"\n\n    local command trimmed_command\n    for command in \"${prompt_command_array[@]:-}\"; do\n        __bp_trim_whitespace trimmed_command \"$command\"\n        if [[ \"$trimmed_command\" == \"$trimmed_arg\" ]]; then\n            return 0\n        fi\n    done\n\n    return 1\n}\n\n# This function is installed as the DEBUG trap.  It is invoked before each\n# interactive prompt display.  Its purpose is to inspect the current\n# environment to attempt to detect if the current command is being invoked\n# interactively, and invoke 'preexec' if so.\n__bp_preexec_invoke_exec() {\n\n    # Save the contents of $_ so that it can be restored later on.\n    # https://stackoverflow.com/questions/40944532/bash-preserve-in-a-debug-trap#40944702\n    __bp_last_argument_prev_command=\"${1:-}\"\n    # Don't invoke preexecs if we are inside of another preexec.\n    if (( __bp_inside_preexec > 0 )); then\n      return\n    fi\n    local __bp_inside_preexec=1\n\n    # Checks if the file descriptor is not standard out (i.e. '1')\n    # __bp_delay_install checks if we're in test. Needed for bats to run.\n    # Prevents preexec from being invoked for functions in PS1\n    if [[ ! -t 1 && -z \"${__bp_delay_install:-}\" ]]; then\n        return\n    fi\n\n    if [[ -n \"${COMP_LINE:-}\" ]]; then\n        # We're in the middle of a completer. This obviously can't be\n        # an interactively issued command.\n        return\n    fi\n    if [[ -z \"${__bp_preexec_interactive_mode:-}\" ]]; then\n        # We're doing something related to displaying the prompt.  Let the\n        # prompt set the title instead of me.\n        return\n    else\n        # If we're in a subshell, then the prompt won't be re-displayed to put\n        # us back into interactive mode, so let's not set the variable back.\n        # In other words, if you have a subshell like\n        #   (sleep 1; sleep 2)\n        # You want to see the 'sleep 2' as a set_command_title as well.\n        if [[ 0 -eq \"${BASH_SUBSHELL:-}\" ]]; then\n            __bp_preexec_interactive_mode=\"\"\n        fi\n    fi\n\n    if  __bp_in_prompt_command \"${BASH_COMMAND:-}\"; then\n        # If we're executing something inside our prompt_command then we don't\n        # want to call preexec. Bash prior to 3.1 can't detect this at all :/\n        __bp_preexec_interactive_mode=\"\"\n        return\n    fi\n\n    local this_command\n    this_command=$(\n        export LC_ALL=C\n        HISTTIMEFORMAT='' builtin history 1 | sed '1 s/^ *[0-9][0-9]*[* ] //'\n    )\n\n    # Sanity check to make sure we have something to invoke our function with.\n    if [[ -z \"$this_command\" ]]; then\n        return\n    fi\n\n    # Invoke every function defined in our function array.\n    local preexec_function\n    local preexec_function_ret_value\n    local preexec_ret_value=0\n    for preexec_function in \"${preexec_functions[@]:-}\"; do\n\n        # Only execute each function if it actually exists.\n        # Test existence of function with: declare -[fF]\n        if type -t \"$preexec_function\" 1>/dev/null; then\n            __bp_set_ret_value \"${__bp_last_ret_value:-}\"\n            # Quote our function invocation to prevent issues with IFS\n            \"$preexec_function\" \"$this_command\"\n            preexec_function_ret_value=\"$?\"\n            if [[ \"$preexec_function_ret_value\" != 0 ]]; then\n                preexec_ret_value=\"$preexec_function_ret_value\"\n            fi\n        fi\n    done\n\n    # Restore the last argument of the last executed command, and set the return\n    # value of the DEBUG trap to be the return code of the last preexec function\n    # to return an error.\n    # If `extdebug` is enabled a non-zero return value from any preexec function\n    # will cause the user's command not to execute.\n    # Run `shopt -s extdebug` to enable\n    __bp_set_ret_value \"$preexec_ret_value\" \"$__bp_last_argument_prev_command\"\n}\n\n__bp_install() {\n    # Exit if we already have this installed.\n    if [[ \"${PROMPT_COMMAND[*]:-}\" == *\"__bp_precmd_invoke_cmd\"* ]]; then\n        return 1;\n    fi\n\n    trap '__bp_preexec_invoke_exec \"$_\"' DEBUG\n\n    # Preserve any prior DEBUG trap as a preexec function\n    local prior_trap\n    # we can't easily do this with variable expansion. Leaving as sed command.\n    # shellcheck disable=SC2001\n    prior_trap=$(sed \"s/[^']*'\\(.*\\)'[^']*/\\1/\" <<<\"${__bp_trap_string:-}\")\n    unset __bp_trap_string\n    if [[ -n \"$prior_trap\" ]]; then\n        eval '__bp_original_debug_trap() {\n          '\"$prior_trap\"'\n        }'\n        preexec_functions+=(__bp_original_debug_trap)\n    fi\n\n    # Adjust our HISTCONTROL Variable if needed.\n    __bp_adjust_histcontrol\n\n    # Issue #25. Setting debug trap for subshells causes sessions to exit for\n    # backgrounded subshell commands (e.g. (pwd)& ). Believe this is a bug in Bash.\n    #\n    # Disabling this by default. It can be enabled by setting this variable.\n    if [[ -n \"${__bp_enable_subshells:-}\" ]]; then\n\n        # Set so debug trap will work be invoked in subshells.\n        set -o functrace > /dev/null 2>&1\n        shopt -s extdebug > /dev/null 2>&1\n    fi;\n\n    local existing_prompt_command\n    # Remove setting our trap install string and sanitize the existing prompt command string\n    existing_prompt_command=\"${PROMPT_COMMAND:-}\"\n    # Edge case of appending to PROMPT_COMMAND\n    existing_prompt_command=\"${existing_prompt_command//$__bp_install_string/:}\" # no-op\n    existing_prompt_command=\"${existing_prompt_command//$'\\n':$'\\n'/$'\\n'}\" # remove known-token only\n    existing_prompt_command=\"${existing_prompt_command//$'\\n':;/$'\\n'}\" # remove known-token only\n    __bp_sanitize_string existing_prompt_command \"$existing_prompt_command\"\n    if [[ \"${existing_prompt_command:-:}\" == \":\" ]]; then\n        existing_prompt_command=\n    fi\n\n    # Install our hooks in PROMPT_COMMAND to allow our trap to know when we've\n    # actually entered something.\n    PROMPT_COMMAND='__bp_precmd_invoke_cmd'\n    PROMPT_COMMAND+=${existing_prompt_command:+$'\\n'$existing_prompt_command}\n    if (( BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 1) )); then\n        PROMPT_COMMAND+=('__bp_interactive_mode')\n    else\n        # shellcheck disable=SC2179 # PROMPT_COMMAND is not an array in bash <= 5.0\n        PROMPT_COMMAND+=$'\\n__bp_interactive_mode'\n    fi\n\n    # Add two functions to our arrays for convenience\n    # of definition.\n    precmd_functions+=(precmd)\n    preexec_functions+=(preexec)\n\n    # Invoke our two functions manually that were added to $PROMPT_COMMAND\n    __bp_precmd_invoke_cmd\n    __bp_interactive_mode\n}\n\n# Sets an installation string as part of our PROMPT_COMMAND to install\n# after our session has started. This allows bash-preexec to be included\n# at any point in our bash profile.\n__bp_install_after_session_init() {\n    # bash-preexec needs to modify these variables in order to work correctly\n    # if it can't, just stop the installation\n    __bp_require_not_readonly PROMPT_COMMAND HISTCONTROL HISTTIMEFORMAT || return\n\n    local sanitized_prompt_command\n    __bp_sanitize_string sanitized_prompt_command \"${PROMPT_COMMAND:-}\"\n    if [[ -n \"$sanitized_prompt_command\" ]]; then\n        # shellcheck disable=SC2178 # PROMPT_COMMAND is not an array in bash <= 5.0\n        PROMPT_COMMAND=${sanitized_prompt_command}$'\\n'\n    fi;\n    # shellcheck disable=SC2179 # PROMPT_COMMAND is not an array in bash <= 5.0\n    PROMPT_COMMAND+=${__bp_install_string}\n}\n\n# Run our install so long as we're not delaying it.\nif [[ -z \"${__bp_delay_install:-}\" ]]; then\n    __bp_install_after_session_init\nfi;\n# -- END BASH-PREEXEC.SH --\n\n}\n\n_install_bash_preexec\nunset -f _install_bash_preexec\n\n# -- BEGIN CODEEDIT CUSTOMIZATIONS --\n\n__codeedit_status=\"$?\"\n\n__codeedit_preexec() {\n    builtin printf \"\\033]0;%s\\007\" \"$1\"\n}\n\n__codeedit_precmd() {\n    builtin printf \"\\033]0;bash\\007\"\n}\n\npreexec_functions+=(__codeedit_preexec)\nprecmd_functions+=(__codeedit_precmd)\n\nif [[ \"$CE_DISABLE_HISTORY\" == \"1\" ]]; then\n    unset HISTFILE\nfi;\n\n# -- END CODEEDIT CUSTOMIZATIONS --\n"
  },
  {
    "path": "CodeEdit/ShellIntegration/codeedit_shell_integration_env.zsh",
    "content": "# Modified from Microsoft's VSCode. MIT License.\n# Permalink to original file:\n# https://github.com/microsoft/vscode/blob/60d7343892f10e0c5f09cb55a6a3f268eb0dd4fb/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-env.zsh\n\nif [[ -f $USER_ZDOTDIR/.zshenv ]]; then\n\tCE_ZDOTDIR=$ZDOTDIR\n\tZDOTDIR=$USER_ZDOTDIR\n\n\t# prevent recursion\n\tif [[ $USER_ZDOTDIR != $CE_ZDOTDIR ]]; then\n\t\t. $USER_ZDOTDIR/.zshenv\n\tfi\n\n\tUSER_ZDOTDIR=$ZDOTDIR\n\tZDOTDIR=$CE_ZDOTDIR\nfi\n"
  },
  {
    "path": "CodeEdit/ShellIntegration/codeedit_shell_integration_login.zsh",
    "content": "# Modified from Microsoft's VSCode. MIT License.\n# Permalink to original file:\n# https://github.com/microsoft/vscode/blob/60d7343892f10e0c5f09cb55a6a3f268eb0dd4fb/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-login.zsh\n\nZDOTDIR=$USER_ZDOTDIR\nif [[ -o \"login\" &&  -f $ZDOTDIR/.zlogin ]]; then\n\t. $ZDOTDIR/.zlogin\nfi\n"
  },
  {
    "path": "CodeEdit/ShellIntegration/codeedit_shell_integration_profile.zsh",
    "content": "# Modified from Microsoft's VSCode. MIT License.\n# Permalink to original file:\n# https://github.com/microsoft/vscode/blob/60d7343892f10e0c5f09cb55a6a3f268eb0dd4fb/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-profile.zsh\n\nif [[ -o \"login\" &&  -f $USER_ZDOTDIR/.zprofile ]]; then\n\tCE_ZDOTDIR=$ZDOTDIR\n\tZDOTDIR=$USER_ZDOTDIR\n\t. $USER_ZDOTDIR/.zprofile\n\tZDOTDIR=$CE_ZDOTDIR\nfi\n"
  },
  {
    "path": "CodeEdit/ShellIntegration/codeedit_shell_integration_rc.zsh",
    "content": "#  codeedit-shell_Integration_rc.zsh\n#  CodeEdit\n#\n#  Created by Qian Qian \"Cubik\" (@Cubik65536) on 2023-06-13.\n#\n#  This script is used to configure zsh/OhMyZsh shells\n#  so the terminal title would be set properly\n#  with shell name or program's command name\n#\n\n# Parts of this file contain modified versions of a source file from Microsoft's VSCode. MIT License.\n# Permalink to original file:\n# https://github.com/microsoft/vscode/blob/60d7343892f10e0c5f09cb55a6a3f268eb0dd4fb/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh\n\n# BEGIN: Modified Microsoft code\n\n# Prevent the script recursing when setting up\nif [ -n \"$CE_SHELL_INTEGRATION\" ]; then\n\tZDOTDIR=$USER_ZDOTDIR\n\tbuiltin return\nfi\n\n# This variable allows the shell to both detect that VS Code's shell integration is enabled as well\n# as disable it by unsetting the variable.\nCE_SHELL_INTEGRATION=1\n\n# By default, zsh will set the $HISTFILE to the $ZDOTDIR location automatically. In the case of the\n# shell integration being injected, this means that the terminal will use a different history file\n# to other terminals. To fix this issue, set $HISTFILE back to the default location before ~/.zshrc\n# is called as that may depend upon the value.\nif [[ \"$CE_INJECTION\" == \"1\" ]]; then\n\tHISTFILE=$USER_ZDOTDIR/.zsh_history\nfi\n\n# Only fix up ZDOTDIR if shell integration was injected (not manually installed) and has not been called yet\nif [[ \"$CE_INJECTION\" == \"1\" ]]; then\n\tif [[ -f $USER_ZDOTDIR/.zshrc ]]; then\n\t\tCE_ZDOTDIR=$ZDOTDIR\n\t\tZDOTDIR=$USER_ZDOTDIR\n\t\t# A user's custom HISTFILE location might be set when their .zshrc file is sourced below\n\t\t. $USER_ZDOTDIR/.zshrc\n\tfi\nfi\n\n# END: Microsoft code\n\nbuiltin autoload -Uz add-zsh-hook\n\n__codeedit_preexec() {\n    builtin printf \"\\033]0;%s\\007\" \"$1\"\n}\n\n__codeedit_precmd() {\n    builtin printf \"\\033]0;zsh\\007\"\n}\n\nadd-zsh-hook preexec __codeedit_preexec\nadd-zsh-hook precmd __codeedit_precmd\n\nif [[ \"$CE_DISABLE_HISTORY\" == \"1\" ]]; then\n    unset HISTFILE\nfi\n\n# Fix ZDOTDIR\n\nif [[ $USER_ZDOTDIR != $CE_ZDOTDIR ]]; then\n\tZDOTDIR=$USER_ZDOTDIR\nfi\n"
  },
  {
    "path": "CodeEdit/Utils/DependencyInjection/LazyServiceWrapper.swift",
    "content": "//\n//  LazyServiceWrapper.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 9/9/24.\n//\n\n/// A property wrapper that provides lazily-loaded access to a service instance.\n///\n/// Using this wrapper, the service is only resolved when the property is first accessed.\n@propertyWrapper\nstruct LazyService<Service> {\n    private let type: ServiceType\n    private var service: Service?\n\n    init(_ type: ServiceType = .singleton) {\n        self.type = type\n    }\n\n    var wrappedValue: Service {\n        mutating get {\n            if let service {\n                return service\n            } else {\n                guard let resolvedService = ServiceContainer.resolve(type, Service.self) else {\n                    let serviceName = String(describing: Service.self)\n                    fatalError(\"No service of type \\(serviceName) registered!\")\n                }\n                self.service = resolvedService\n                return resolvedService\n            }\n        } mutating set {\n            self.service = newValue\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/DependencyInjection/ServiceContainer.swift",
    "content": "//\n//  ServiceContainer.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 4/3/24.\n//\n\nimport Foundation\n\n/// A service container that manages the registration and resolution of services.\nenum ServiceContainer {\n    /// A dictionary storing the closures for creating service instances.\n    private static var factories: [ObjectIdentifier: () -> Any] = [:]\n    /// A dictionary storing the cached service instances.\n    private static var cache: [ObjectIdentifier: Any] = [:]\n    /// A dispatch queue used for synchronizing access to the factories and cache.\n    private static let queue = DispatchQueue(label: \"ServiceContainerQueue\")\n\n    /// Registers a factory closure for creating instances of a service type.\n    ///\n    /// - Parameter factory: An autoclosure that returns an instance of the service type.\n    static func register<Service>(_ factory: @autoclosure @escaping () -> Service) {\n        queue.sync {\n            let key = ObjectIdentifier(Service.Type.self)\n            factories[key] = factory\n        }\n    }\n\n    /// Resolves an instance of a service type based on the specified resolution type.\n    ///\n    /// - Parameters:\n    ///   - resolveType: The type of resolution to use for the service. Defaults to `.singleton`.\n    ///   - type: The type of the service to resolve.\n    /// - Returns: An instance of the resolved service type, or `nil` if the service is not registered.\n    static func resolve<Service>(_ resolveType: ServiceType = .singleton, _ type: Service.Type) -> Service? {\n        let serviceId = ObjectIdentifier(Service.Type.self)\n\n        return queue.sync {\n            switch resolveType {\n            case .singleton:\n                if let service = cache[serviceId] as? Service {\n                    return service\n                } else {\n                    let service = factories[serviceId]?() as? Service\n\n                    if let service = service {\n                        cache[serviceId] = service\n                    }\n\n                    return service\n                }\n            case .newSingleton:\n                let service = factories[serviceId]?() as? Service\n\n                if let service = service {\n                    cache[serviceId] = service\n                }\n\n                return service\n            case .new:\n                return factories[serviceId]?() as? Service\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/DependencyInjection/ServiceType.swift",
    "content": "//\n//  ServiceType.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 4/3/24.\n//\n\n/// Defines the type of service instantiation strategy.\nenum ServiceType {\n    /// Returns a new singleton on the first call, then returns a cached one every other time\n    case singleton\n    /// Creates a new singleton reference each time and caches it, returning the newer singleton\n    case newSingleton\n    /// Creates a new singleton\n    case new\n}\n"
  },
  {
    "path": "CodeEdit/Utils/DependencyInjection/ServiceWrapper.swift",
    "content": "//\n//  ServiceWrapper.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 4/3/24.\n//\n\n/// A property wrapper that provides access to a service instance.\n@propertyWrapper\nstruct Service<Service> {\n    var service: Service\n\n    init(_ type: ServiceType = .singleton) {\n        guard let service = ServiceContainer.resolve(type, Service.self) else {\n            let serviceName = String(describing: Service.self)\n            fatalError(\"No service of type \\(serviceName) registered!\")\n        }\n\n        self.service = service\n    }\n\n    var wrappedValue: Service {\n        get { self.service }\n        mutating set { service = newValue }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Environment/Env+IsFullscreen.swift",
    "content": "//\n//  Env+IsFullscreen.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 14/01/2023.\n//\n\nimport SwiftUI\n\nprivate struct WorkspaceFullscreenStateEnvironmentKey: EnvironmentKey {\n    static let defaultValue: Bool = false\n}\n\nextension EnvironmentValues {\n    var isFullscreen: Bool {\n        get { self[WorkspaceFullscreenStateEnvironmentKey.self] }\n        set { self[WorkspaceFullscreenStateEnvironmentKey.self] = newValue }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Environment/Env+Window.swift",
    "content": "//\n//  Env+Window.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 14/01/2023.\n//\n\nimport SwiftUI\n\nstruct WindowBox {\n    weak var value: NSWindow?\n}\n\nstruct NSWindowEnvironmentKey: EnvironmentKey {\n    typealias Value = WindowBox\n    static var defaultValue = WindowBox(value: nil)\n}\n\nextension EnvironmentValues {\n    var window: WindowBox {\n        get { self[NSWindowEnvironmentKey.self] }\n        set { self[NSWindowEnvironmentKey.self] = newValue }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/Array/Array+Index.swift",
    "content": "//\n//  Array+Index.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 7/24/25.\n//\n\nextension Array {\n    var second: Element? {\n        self.count > 1 ? self[1] : nil\n    }\n\n    var third: Element? {\n        self.count > 2 ? self[2] : nil\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/Array/Array+SortURLs.swift",
    "content": "//\n//  Array+FileSystem.FileItem.swift\n//  CodeEdit\n//\n//  Created by Matthijs Eikelenboom on 07/02/2023.\n//\n\nimport Foundation\n\nextension Array where Element == URL {\n\n    /// Sorts the elements in alphabetical order.\n    /// - Parameter foldersOnTop: if set to `true` folders will always be on top of files.\n    /// - Returns: A sorted array of `URL`\n    func sortItems(foldersOnTop: Bool) -> [URL] {\n        return self.sorted { lhs, rhs in\n            let lhsIsDir = (try? lhs.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) ?? false\n            let rhsIsDir = (try? rhs.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) ?? false\n\n            if foldersOnTop {\n                if lhsIsDir != rhsIsDir {\n                    return lhsIsDir\n                }\n            }\n\n            return compareNaturally(lhs.lastPathComponent, rhs.lastPathComponent)\n        }\n    }\n\n    /// Compare two strings using natural sorting.\n    /// - Parameters:\n    ///   - lhs: The left-hand string.\n    ///   - rhs: The right-hand string.\n    /// - Returns: `true` if `lhs` should be ordered before `rhs`.\n    private func compareNaturally(_ lhs: String, _ rhs: String) -> Bool {\n        let lhsComponents = lhs.components(separatedBy: CharacterSet.decimalDigits.inverted)\n        let rhsComponents = rhs.components(separatedBy: CharacterSet.decimalDigits.inverted)\n\n        for (lhsPart, rhsPart) in zip(lhsComponents, rhsComponents) where lhsPart != rhsPart {\n            if let lhsNum = Int(lhsPart), let rhsNum = Int(rhsPart) {\n                return lhsNum < rhsNum\n            } else {\n                return lhsPart < rhsPart\n            }\n        }\n\n        return lhs < rhs\n    }\n}\n\nextension Array where Element: Hashable {\n\n    /// Checks the difference between two given items.\n    /// - Parameter other: Other element\n    /// - Returns: symmetricDifference\n    func difference(from other: [Element]) -> [Element] {\n        let thisSet = Set(self)\n        let otherSet = Set(other)\n        return Array(thisSet.symmetricDifference(otherSet))\n    }\n\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/Bundle/Bundle+Info.swift",
    "content": "//\n//  Bundle+Info.swift\n//  CodeEditModules/CodeEditUtils\n//\n//  Created by Lukas Pistrol on 01.05.22.\n//\n\nimport Foundation\n\nextension Bundle {\n\n    static var appName: String {\n        Bundle.main.object(forInfoDictionaryKey: \"CFBundleName\") as? String ?? \"Unknown App\"\n    }\n\n    static var displayName: String {\n        Bundle.main.object(forInfoDictionaryKey: \"CFBundleDisplayName\") as? String\n        ?? Bundle.main.object(forInfoDictionaryKey: \"CFBundleName\") as? String\n        ?? \"Unknown App\"\n    }\n\n    static var copyrightString: String? {\n        Bundle.main.object(forInfoDictionaryKey: \"NSHumanReadableCopyright\") as? String\n    }\n\n    /// Returns the main bundle's version string if available (e.g. 1.0.0)\n    static var versionString: String? {\n        Bundle.main.object(forInfoDictionaryKey: \"CFBundleShortVersionString\") as? String\n    }\n\n    /// Returns the main bundle's build string if available (e.g. 123)\n    static var buildString: String? {\n        Bundle.main.object(forInfoDictionaryKey: \"CFBundleVersion\") as? String\n    }\n\n    static var versionPostfix: String? {\n        Bundle.main.object(forInfoDictionaryKey: \"CE_VERSION_POSTFIX\") as? String\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/Collection/Collection+subscript_safe.swift",
    "content": "//\n//  Collection+subscript_safe.swift\n//  CodeEdit\n//\n//  Created by Paul Ebose on 2024/07/05.\n//\n\nimport Foundation\n\nextension Collection {\n    /// Returns the element at the specified index if it is within bounds, otherwise nil.\n    subscript (safe index: Index) -> Element? {\n        indices.contains(index) ? self[index] : nil\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/Color/Color+HEX.swift",
    "content": "//\n//  Color+HEX.swift\n//  CodeEditModules/CodeEditUtils\n//\n//  Created by Lukas Pistrol on 23.03.22.\n//\n\nimport SwiftUI\n\nextension Color {\n\n    /// Initializes a `Color` from a HEX String (e.g.: `#1D2E3F`) and an optional alpha value.\n    /// - Parameters:\n    ///   - hex: A String of a HEX representation of a color (format: `#1D2E3F`)\n    ///   - alpha: A Double indicating the alpha value from `0.0` to `1.0`\n    init(hex: String, alpha: Double = 1.0) {\n        let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)\n        var int: UInt64 = 0\n        Scanner(string: hex).scanHexInt64(&int)\n        self.init(hex: Int(int), alpha: alpha)\n    }\n\n    /// Initializes a `Color` from an Int (e.g.: `0x1D2E3F`)and an optional alpha value.\n    /// - Parameters:\n    ///   - hex: An Int of a HEX representation of a color (format: `0x1D2E3F`)\n    ///   - alpha: A Double indicating the alpha value from `0.0` to `1.0`\n    init(hex: Int, alpha: Double = 1.0) {\n        let red = (hex >> 16) & 0xFF\n        let green = (hex >> 8) & 0xFF\n        let blue = hex & 0xFF\n        self.init(.sRGB, red: Double(red) / 255, green: Double(green) / 255, blue: Double(blue) / 255, opacity: alpha)\n    }\n\n    /// Returns an Int representing the `Color` in hex format (e.g.: 0x112233)\n    var hex: Int {\n        guard let components = cgColor?.components, components.count >= 3 else { return 0 }\n\n        let red = lround((Double(components[0]) * 255.0)) << 16\n        let green = lround((Double(components[1]) * 255.0)) << 8\n        let blue = lround((Double(components[2]) * 255.0))\n\n        return red | green | blue\n    }\n\n    /// Returns a HEX String representing the `Color` (e.g.: #112233)\n    var hexString: String {\n        let color = self.hex\n\n        return \"#\" + String(format: \"%06x\", color)\n    }\n\n    /// The alpha (opacity) component of the Color (0.0 - 1.0)\n    var alphaComponent: Double {\n        NSColor(self).alphaComponent\n    }\n}\n\nextension NSColor {\n\n    /// Initializes a `NSColor` from a HEX String (e.g.: `#1D2E3F`) and an optional alpha value.\n    /// - Parameters:\n    ///   - hex: A String of a HEX representation of a color (format: `#1D2E3F`)\n    ///   - alpha: A Double indicating the alpha value from `0.0` to `1.0`\n    convenience init(hex: String, alpha: Double = 1.0) {\n        let hex = hex.trimmingCharacters(in: .alphanumerics.inverted)\n        var int: UInt64 = 0\n        Scanner(string: hex).scanHexInt64(&int)\n        self.init(hex: Int(int), alpha: alpha)\n    }\n\n    /// Initializes a `NSColor` from an Int  (e.g.: `0x1D2E3F`)and an optional alpha value.\n    /// - Parameters:\n    ///   - hex: An Int of a HEX representation of a color (format: `0x1D2E3F`)\n    ///   - alpha: A Double indicating the alpha value from `0.0` to `1.0`\n    convenience init(hex: Int, alpha: Double = 1.0) {\n        let red = (hex >> 16) & 0xFF\n        let green = (hex >> 8) & 0xFF\n        let blue = hex & 0xFF\n        self.init(srgbRed: Double(red) / 255, green: Double(green) / 255, blue: Double(blue) / 255, alpha: alpha)\n    }\n\n    /// Returns an Int representing the `NSColor` in hex format (e.g.: 0x112233)\n    var hex: Int {\n        guard let components = cgColor.components, components.count >= 3 else { return 0 }\n\n        let red = lround((Double(components[0]) * 255.0)) << 16\n        let green = lround((Double(components[1]) * 255.0)) << 8\n        let blue = lround((Double(components[2]) * 255.0))\n\n        return red | green | blue\n    }\n\n    /// Returns a HEX String representing the `NSColor` (e.g.: #112233)\n    var hexString: String {\n        let color = self.hex\n\n        return \"#\" + String(format: \"%06x\", color)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/Date/Date+Formatted.swift",
    "content": "//\n//  Date+Formatted.swift\n//  CodeEditModules/CodeEditUtils\n//\n//  Created by Lukas Pistrol on 20.04.22.\n//\n\nimport Foundation\n\nextension Date {\n\n    /// Returns a formatted & localized string of a relative duration compared to the current date & time\n    /// when the date is in `today` or `yesterday`. Otherwise it returns a formatted date in `short`\n    /// format. The time is omitted.\n    /// - Parameter locale: The locale. Defaults to `Locale.current`\n    /// - Returns: A localized formatted string\n    func relativeStringToNow(locale: Locale = .current) -> String {\n        if Calendar.current.isDateInToday(self) ||\n            Calendar.current.isDateInYesterday(self) {\n            var style = RelativeFormatStyle(\n                presentation: .named,\n                unitsStyle: .abbreviated,\n                locale: .current,\n                calendar: .current,\n                capitalizationContext: .standalone\n            )\n\n            style.locale = locale\n\n            return self.formatted(style)\n        }\n        let formatter = DateFormatter()\n        formatter.dateStyle = .short\n        formatter.timeStyle = .none\n        formatter.locale = locale\n\n        return formatter.string(from: self)\n    }\n\n    static var logFormatter: DateFormatter = {\n        let formatter = DateFormatter()\n        formatter.dateFormat = \"HH:mm:ss.SSSS\"\n        return formatter\n    }()\n\n    func logFormatted() -> String {\n        Self.logFormatter.string(from: self)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/FileManager/FileManager+MakeExecutable.swift",
    "content": "//\n//  FileManager+MakeExecutable.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 8/14/25.\n//\n\nimport Foundation\n\nextension FileManager {\n    /// Make a given URL executable via POSIX permissions.\n    /// Slightly different from `chmod +x`, does not give execute permissions for users besides\n    /// the current user.\n    /// - Parameter executableURL: The URL of the file to make executable.\n    func makeExecutable(_ executableURL: URL) throws {\n        let fileAttributes = try FileManager.default.attributesOfItem(\n            atPath: executableURL.path(percentEncoded: false)\n        )\n        guard var permissions = fileAttributes[.posixPermissions] as? UInt16 else { return }\n        permissions |= 0b001_000_000 // Execute perms for user, not group, not others\n        try FileManager.default.setAttributes(\n            [.posixPermissions: permissions],\n            ofItemAtPath: executableURL.path(percentEncoded: false)\n        )\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/Int/Int+HexString.swift",
    "content": "//\n//  Int+HexString.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 6/13/25.\n//\n\nextension UInt {\n    init?(hexString: String) {\n        // Trim 0x if it's there\n        let string = String(hexString.trimmingPrefix(\"0x\"))\n        guard let value = UInt(string, radix: 16) else {\n            return nil\n        }\n        self = value\n    }\n}\n\nextension Int {\n    init?(hexString: String) {\n        // Trim 0x if it's there\n        let string = String(hexString.trimmingPrefix(\"0x\"))\n        guard let value = Int(string, radix: 16) else {\n            return nil\n        }\n        self = value\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/LanguageIdentifier/LanguageIdentifier+CodeLanguage.swift",
    "content": "//\n//  LanguageIdentifier+CodeLanguage.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 9/9/24.\n//\n\nimport LanguageServerProtocol\nimport CodeEditLanguages\n\nextension CodeLanguage {\n    var lspLanguage: LanguageIdentifier? {\n        switch self.id {\n        case .agda,\n                .bash,\n                .haskell,\n                .julia,\n                .kotlin,\n                .ocaml,\n                .ocamlInterface,\n                .regex,\n                .toml,\n                .verilog,\n                .zig,\n                .plainText:\n            return nil\n        case .c:\n            return .c\n        case .cpp:\n            return .cpp\n        case .cSharp:\n            return .csharp\n        case .css:\n            return .css\n        case .dart:\n            return .dart\n        case .dockerfile:\n            return .dockerfile\n        case .elixir:\n            return .elixir\n        case .go, .goMod:\n            return  .go\n        case .html:\n            return .html\n        case .java:\n            return .java\n        case .javascript, .jsdoc:\n            return .javascript\n        case .json:\n            return .json\n        case .jsx:\n            return .javascriptreact\n        case .lua:\n            return .lua\n        case .markdown, .markdownInline:\n            return .markdown\n        case .objc:\n            return .objc\n        case .perl:\n            return .perl\n        case .php:\n            return .php\n        case .python:\n            return .python\n        case .ruby:\n            return .ruby\n        case .rust:\n            return .rust\n        case .scala:\n            return .scala\n        case .sql:\n            return .sql\n        case .swift:\n            return .swift\n        case .tsx:\n            return .typescriptreact\n        case .typescript:\n            return .typescript\n        case .yaml:\n            return .yaml\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/LocalProcess/LocalProcess+sendText.swift",
    "content": "//\n//  LocalProcess+sendText.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/15/25.\n//\n\nimport SwiftTerm\n\nextension LocalProcess {\n    func send(text: String) {\n        let array = Array(text.utf8)\n        self.send(data: array[0..<array.count])\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/NSApplication/NSApp+openWindow.swift",
    "content": "//\n//  NSApp+openWindow.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 14/03/2023.\n//\n\nimport AppKit\nimport SwiftUI\n\nextension OpenWindowAction {\n    func callAsFunction(sceneID: SceneID) {\n        callAsFunction(id: sceneID.rawValue)\n    }\n}\n\nextension NSApplication {\n    func closeWindow(_ id: SceneID) {\n        windows.first { $0.identifier?.rawValue == id.rawValue }?.close()\n    }\n\n    func closeWindow(_ ids: SceneID...) {\n        ids.forEach { id in\n            windows.first { $0.identifier?.rawValue == id.rawValue }?.close()\n        }\n    }\n\n    func findWindow(_ id: SceneID) -> NSWindow? {\n        windows.first { $0.identifier?.rawValue == id.rawValue }\n    }\n\n    var openSwiftUIWindows: Int {\n        NSApp\n            .windows\n            .compactMap(\\.identifier?.rawValue)\n            .compactMap { SceneID(rawValue: $0) }\n            .count\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/NSTableView/NSTableView+Background.swift",
    "content": "//\n//  NSTableView+Background.swift\n//  CodeEdit\n//\n//  Created by Lukas Pistrol on 20.04.22.\n//\n\nimport SwiftUI\n\nextension NSTableView {\n    /// Allows to set a lists background color in SwiftUI\n    override open func viewDidMoveToWindow() {\n        super.viewDidMoveToWindow()\n\n        backgroundColor = NSColor.clear\n        enclosingScrollView?.drawsBackground = false\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/NSWindow/NSWindow+Child.swift",
    "content": "//\n//  NSWindow+Child.swift\n//  CodeEdit\n//\n//  Created by Axel Martinez on 8/4/24.\n//\n\nimport AppKit\n\nextension NSWindow {\n    func addCenteredChildWindow(_ childWindow: NSWindow, over parentWindow: NSWindow) {\n        let parentFrame = parentWindow.frame\n        let parentCenterX = parentFrame.origin.x + (parentFrame.size.width / 2)\n        let parentCenterY = parentFrame.origin.y + (parentFrame.size.height / 2)\n\n        let childWidth = childWindow.frame.size.width\n        let childHeight = childWindow.frame.size.height\n        let newChildOriginX = parentCenterX - (childWidth / 2)\n        let newChildOriginY = parentCenterY - (childHeight / 2)\n\n        childWindow.setFrameOrigin(NSPoint(x: newChildOriginX, y: newChildOriginY))\n\n        parentWindow.addChildWindow(childWindow, ordered: .above)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/OperatingSystemVersion/OperatingSystemVersion+String.swift",
    "content": "//\n//  OperatingSystemVersion+String.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 5/27/25.\n//\n\nimport Foundation\n\nextension OperatingSystemVersion {\n    var semverString: String {\n        \"\\(majorVersion).\\(minorVersion).\\(patchVersion)\"\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/SemanticToken/SemanticToken+Position.swift",
    "content": "//\n//  SemanticToken+Position.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 12/26/24.\n//\n\nimport LanguageServerProtocol\n\nextension SemanticToken {\n    var startPosition: Position {\n        Position(line: Int(line), character: Int(char))\n    }\n\n    var endPosition: Position {\n        Position(line: Int(line), character: Int(char + length))\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/String/String+AppearancesOfSubstring.swift",
    "content": "//\n//  String+AppearancesOfSubstring.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 24.11.23.\n//\n\nimport Foundation\n\nextension String {\n    /// Finds the appearances of a substring within the string.\n    /// - Parameters:\n    ///   - substring: The substring to search for within the string.\n    ///   - toLeft: The optional number of characters to include to the left of each found substring appearance.\n    ///   - toRight: The optional number of characters to include to the right of each found substring appearance.\n    ///\n    ///   - Returns: An array of ranges representing the appearances of the substring within the string.\n    func appearancesOfSubstring(substring: String, toLeft: Int=0, toRight: Int=0) -> [Range<String.Index>] {\n        guard !substring.isEmpty && self.contains(substring) else { return [] }\n        var appearances: [Range<String.Index>] = []\n        for (index, character) in self.enumerated() where character == substring.first {\n            let startOfFoundCharacter = self.index(self.startIndex, offsetBy: index)\n            guard index + substring.count < self.count else { continue }\n            let lengthOfFoundCharacter = self.index(self.startIndex, offsetBy: (substring.count + index))\n            if self[startOfFoundCharacter..<lengthOfFoundCharacter] == substring {\n                let startIndex = self.index(\n                    self.startIndex,\n                    offsetBy: index - (toLeft <= index ? toLeft : 0)\n                )\n                let endIndex = self.index(\n                    self.startIndex,\n                    offsetBy: substring.count + index + (substring.count+index+toRight <= self.count ? toRight : 0)\n                )\n                appearances.append(startIndex..<endIndex)\n            }\n        }\n        return appearances\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/String/String+Character.swift",
    "content": "//\n//  String+Character.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 24.11.23.\n//\n\nimport Foundation\n\nextension String {\n    /// Retrieves the character at the specified index within the string.\n    /// - Parameter index: The index of the character to retrieve.\n    /// - Returns: The character at the specified index.\n    func character(at index: Int) -> Character? {\n        guard index < self.count else {\n            return nil\n        }\n\n        return self[self.index(self.startIndex, offsetBy: index)]\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/String/String+Escaped.swift",
    "content": "//\n//  String+escapedWhiteSpaces.swift\n//  CodeEdit\n//\n//  Created by Paul Ebose on 2024/07/05.\n//\n\nimport Foundation\n\nextension String {\n    /// Escapes the string so it's an always-valid directory\n    func escapedDirectory() -> String {\n        \"\\\"\\(self.escapedQuotes())\\\"\"\n    }\n\n    /// Returns a new string, replacing all occurrences of ` ` with `\\ ` if they aren't already escaped.\n    func escapedWhiteSpaces() -> String {\n        escape(replacing: \" \")\n    }\n\n    /// Returns a new string, replacing all occurrences of `\"` with `\\\"` if they aren't already escaped.\n    func escapedQuotes() -> String {\n        escape(replacing: #\"\"\"#)\n    }\n\n    func escape(replacing: Character) -> String {\n        var string = \"\"\n        var lastChar: Character?\n\n        for char in self {\n            defer {\n                lastChar = char\n            }\n\n            guard char == replacing else {\n                string.append(char)\n                continue\n            }\n\n            if let lastChar, lastChar == #\"\\\"# {\n                string.append(char)\n                continue\n            }\n\n            string.append(#\"\\\"#)\n            string.append(char)\n        }\n\n        return string\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/String/String+HighlightOccurrences.swift",
    "content": "//\n//  String+HighlightOccurrences.swift\n//  CodeEdit\n//\n//  Created by Raymond Vleeshouwer on 13/06/23.\n//\n\nimport Foundation\nimport SwiftUI\n\nextension String {\n    /// Highlights occurences of a substring in a string and returns text highlighted as such\n    func highlightOccurrences(_ ofSearch: String) -> some View {\n        if ofSearch.isEmpty {\n            return Text(self)\n        }\n\n        let ranges = self.rangesOfSubstring(ofSearch.lowercased())\n\n        var currentIndex = self.startIndex\n        var highlightedText = Text(\"\")\n\n        for range in ranges {\n            let nonHighlightedText = self[currentIndex..<range.lowerBound]\n            let highlightedSubstring = self[range]\n\n            // swiftlint:disable shorthand_operator\n            highlightedText = highlightedText + Text(nonHighlightedText).foregroundColor(.secondary)\n            highlightedText = highlightedText + Text(highlightedSubstring).foregroundColor(.primary).bold()\n\n            currentIndex = range.upperBound\n        }\n\n        let remainingText = self[currentIndex..<self.endIndex]\n\n        return highlightedText + Text(remainingText).foregroundColor(.secondary)\n        // swiftlint:enable shorthand_operator\n    }\n\n    private func rangesOfSubstring(_ substring: String) -> [Range<Index>] {\n        var ranges = [Range<Index>]()\n        var currentIndex = self.startIndex\n\n        while let range = self.range(\n            of: substring,\n            options: .caseInsensitive,\n            range: currentIndex..<self.endIndex\n        ) {\n            ranges.append(range)\n\n            currentIndex = range.upperBound\n        }\n\n        return ranges\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/String/String+Lines.swift",
    "content": "//\n//  String+Lines.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 6/17/23.\n//\n\nimport Foundation\n\nextension String {\n    /// Calculates the first `n` lines and returns them as a new string.\n    /// - Parameters:\n    ///   - lines: The number of lines to return.\n    ///   - maxLength: The maximum number of characters to copy.\n    /// - Returns: A new string containing the lines.\n    func getFirstLines(_ lines: Int = 1, maxLength: Int = 512) -> String {\n        var string = \"\"\n        var foundLines = 0\n        var totalLength = 0\n        for char in self.lazy {\n            if char.isNewline {\n                foundLines += 1\n            }\n            totalLength += 1\n            if foundLines >= lines || totalLength >= maxLength {\n                break\n            }\n            string.append(char)\n        }\n        return string\n    }\n\n    /// Calculates the last `n` lines and returns them as a new string.\n    /// - Parameters:\n    ///   - lines: The number of lines to return.\n    ///   - maxLength: The maximum number of characters to copy.\n    /// - Returns: A new string containing the lines.\n    func getLastLines(_ lines: Int = 1, maxLength: Int = 512) -> String {\n        var string = \"\"\n        var foundLines = 0\n        var totalLength = 0\n        for char in self.lazy.reversed() {\n            if char.isNewline {\n                foundLines += 1\n            }\n            totalLength += 1\n            if foundLines >= lines || totalLength >= maxLength {\n                break\n            }\n            string = String(char) + string\n        }\n        return string\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/String/String+MD5.swift",
    "content": "//\n//  String+MD5.swift\n//  CodeEditModules/CodeEditUtils\n//\n//  Created by Nanashi Li on 2022/04/19.\n//\n\nimport Foundation\nimport CryptoKit\n\nextension String {\n\n    /// Returns a MD5 encrypted String of the input String\n    ///\n    /// - Parameters:\n    ///   - trim: If `true` the input string will be trimmed from whitespaces and new-lines. Defaults to `false`.\n    ///   - caseSensitive: If `false` the input string will be converted to lowercase characters. Defaults to `true`.\n    /// - Returns: A String in HEX format\n    func md5(trim: Bool = false, caseSensitive: Bool = true) -> String {\n        var string = self\n\n        // trim whitespaces & new lines if specifiedå\n        if trim { string = string.trimmingCharacters(in: .whitespacesAndNewlines) }\n\n        // make string lowercased if not case sensitive\n        if !caseSensitive { string = string.lowercased() }\n\n        // compute the hash\n        // (note that `String.data(using: .utf8)!` is safe since it will never fail)\n        let computed = Insecure.MD5.hash(data: string.data(using: .utf8)!)\n\n        // map the result to a hex string and return\n        return computed.compactMap { String(format: \"%02x\", $0) }.joined()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/String/String+Ranges.swift",
    "content": "//\n//  String+Ranges.swift\n//  CodeEdit\n//\n//  Created by Ziyuan Zhao on 2022/3/21.\n//\n\nimport Foundation\n\nextension StringProtocol where Index == String.Index {\n    func ranges<T: StringProtocol>(\n        of substring: T,\n        options: String.CompareOptions = [],\n        locale: Locale? = nil\n    ) -> [Range<Index>] {\n        var ranges: [Range<Index>] = []\n        while let result = range(\n            of: substring,\n            options: options,\n            range: (ranges.last?.upperBound ?? startIndex)..<endIndex,\n            locale: locale\n        ) {\n            ranges.append(result)\n        }\n        return ranges\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/String/String+RemoveOccurrences.swift",
    "content": "//\n//  String+RemoveOccurrences.swift\n//  CodeEditModules/CodeEditUtils\n//\n//  Created by Lukas Pistrol on 24.04.22.\n//\n\nimport Foundation\n\nextension String {\n\n    /// Removes all `new-line` characters in a `String`\n    /// - Returns: A String\n    func removingNewLines() -> String {\n        self.replacingOccurrences(of: \"\\n\", with: \"\")\n    }\n\n    /// Removes all `space` characters in a `String`\n    /// - Returns: A String\n    func removingSpaces() -> String {\n        self.replacingOccurrences(of: \" \", with: \"\")\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/String/String+SHA256.swift",
    "content": "//\n//  String+SHA256.swift\n//  CodeEditModules/CodeEditUtils\n//\n//  Created by Debdut Karmakar on 6/9/22.\n//\n\nimport Foundation\nimport CryptoKit\n\nextension String {\n\n    /// Returns a SHA256 encrypted String of the input String\n    ///\n    /// - Parameters:\n    ///   - trim: If `true` the input string will be trimmed from whitespaces and new-lines. Defaults to `false`.\n    ///   - caseSensitive: If `false` the input string will be converted to lowercase characters. Defaults to `true`.\n    /// - Returns: A String in HEX format\n    func sha256(trim: Bool = false, caseSensitive: Bool = true) -> String {\n        var string = self\n\n        // trim whitespaces & new lines if specified\n        if trim { string = string.trimmingCharacters(in: .whitespacesAndNewlines) }\n\n        // make string lowercased if not case sensitive\n        if !caseSensitive { string = string.lowercased() }\n\n        // compute the hash\n        // (note that `String.data(using: .utf8)!` is safe since it will never fail)\n        let computed = SHA256.hash(data: string.data(using: .utf8)!)\n\n        // map the result to a hex string and return\n        return computed.compactMap { String(format: \"%02x\", $0) }.joined()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/String/String+ValidFileName.swift",
    "content": "//\n//  String+ValidFileName.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 1/13/25.\n//\n\nimport Foundation\n\nextension CharacterSet {\n    /// On macOS, valid file names must not contain the `NULL` or `:` characters.\n    static var invalidFileNameCharacters: CharacterSet = CharacterSet(charactersIn: \"\\0:\")\n}\n\nextension String {\n    /// On macOS, valid file names must not contain the `NULL` or `:` characters, must be non-empty, and must be less\n    /// than 256 UTF16 characters.\n    var isValidFilename: Bool {\n        !isEmpty && CharacterSet(charactersIn: self).isDisjoint(with: .invalidFileNameCharacters) && utf16.count < 256\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/SwiftTerm/Color/SwiftTerm+Color+Init.swift",
    "content": "//\n//  SwiftTerm+Color+Init.swift\n//  CodeEditModules/TerminalEmulator\n//\n//  Created by Lukas Pistrol on 24.03.22.\n//\n\nimport Foundation\nimport SwiftTerm\n\nextension SwiftTerm.Color {\n    /// 0.0-1.0\n    convenience init(dRed red: Double, green: Double, blue: Double) {\n        let multiplier: Double = 65535\n        self.init(\n            red: UInt16(red * multiplier),\n            green: UInt16(green * multiplier),\n            blue: UInt16(blue * multiplier)\n        )\n    }\n\n    /// 0-255\n    convenience init(iRed red: UInt8, green: UInt8, blue: UInt8) {\n        let divisor: Double = 255\n        self.init(\n            dRed: Double(red) / divisor,\n            green: Double(green) / divisor,\n            blue: Double(blue) / divisor\n        )\n    }\n\n    /// 0x000000 - 0xFFFFFF\n    convenience init(hex: Int) {\n        let red = UInt8((hex >> 16) & 0xFF)\n        let green = UInt8((hex >> 8) & 0xFF)\n        let blue = UInt8(hex & 0xFF)\n        self.init(iRed: red, green: green, blue: blue)\n    }\n\n    /// 0x000000 - 0xFFFFFF\n    convenience init(hex: String) {\n        let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)\n        var int: UInt64 = 0\n        Scanner(string: hex).scanHexInt64(&int)\n        self.init(hex: Int(int))\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/Text/Font+Caption3.swift",
    "content": "//\n//  Font+Caption3.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 19/01/2023.\n//\n\nimport SwiftUI\n\nextension Font {\n    static var caption3: Font { .system(size: 11, weight: .medium) }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/TextView/TextView+LSPRange.swift",
    "content": "//\n//  TextView+LSPRange.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 9/21/24.\n//\n\nimport AppKit\nimport CodeEditTextView\nimport LanguageServerProtocol\n\nextension TextView {\n    func lspRangeFrom(nsRange: NSRange) -> LSPRange? {\n        guard let startLine = layoutManager.textLineForOffset(nsRange.location),\n              let endLine = layoutManager.textLineForOffset(nsRange.max) else {\n            return nil\n        }\n        return LSPRange(\n            start: Position(line: startLine.index, character: nsRange.location - startLine.range.location),\n            end: Position(line: endLine.index, character: nsRange.max - endLine.range.location)\n        )\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/TextView/TextView+SemanticTokenRangeProvider.swift",
    "content": "//\n//  TextView+SemanticTokenRangeProvider.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 12/19/24.\n//\n\nimport Foundation\nimport CodeEditTextView\nimport LanguageServerProtocol\n\nextension TextView: SemanticTokenMapRangeProvider {\n    func nsRangeFrom(_ range: SemanticTokenRange) -> NSRange? {\n        nsRangeFrom(line: range.line, char: range.char, length: range.length)\n    }\n\n    func nsRangeFrom(line: UInt32, char: UInt32, length: UInt32) -> NSRange? {\n        guard let line = layoutManager.textLineForIndex(Int(line)) else {\n            return nil\n        }\n        return NSRange(location: line.range.location + Int(char), length: Int(length))\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/URL/URL+Filename.swift",
    "content": "//\n//  URL+Filename.swift\n//  CodeEdit\n//\n//  Created by Axel Martinez on 5/8/24.\n//\n\nimport Foundation\n\nextension URL {\n    var fileName: String {\n        self.lastPathComponent.trimmingCharacters(in: .whitespacesAndNewlines)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/URL/URL+FindWorkspace.swift",
    "content": "//\n//  URL+FindWorkspace.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 12/19/24.\n//\n\nimport Foundation\n\nextension URL {\n    /// Finds a workspace that contains the url.\n    func findWorkspace() -> WorkspaceDocument? {\n        CodeEditDocumentController.shared.documents.first(where: { doc in\n            guard let workspace = doc as? WorkspaceDocument else { return false }\n            // createIfNotFound is safe here because it will still exit if the file and the workspace\n            // do not share a path prefix\n            return workspace.workspaceFileManager?.getFile(absolutePath, createIfNotFound: true) != nil\n        }) as? WorkspaceDocument\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/URL/URL+FuzzySearchable.swift",
    "content": "//\n//  URL+FuzzySearchable.swift\n//  CodeEdit\n//\n//  Created by Tommy Ludwig on 03.02.24.\n//\n\nimport Foundation\n\nextension URL: FuzzySearchable {\n    var searchableString: String {\n        return self.lastPathComponent\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/URL/URL+Identifiable.swift",
    "content": "//\n//  URL+Identifiable.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 3/5/25.\n//\n\nimport Foundation\n\nextension URL: @retroactive Identifiable {\n    public var id: String {\n        absoluteString\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/URL/URL+LSPURI.swift",
    "content": "//\n//  URL+LSPURI.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 3/24/25.\n//\n\nimport Foundation\n\nextension URL {\n    /// A stable string to use when identifying documents with language servers.\n    /// Needs to be a valid URI, so always returns with the `file://` prefix to indicate it's a file URI.\n    ///\n    /// Use this whenever possible when using USLs in LSP processing if not using the ``LanguageServerDocument`` type.\n    var lspURI: String {\n        return \"file://\" + absolutePath\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/URL/URL+ResouceValues.swift",
    "content": "//\n//  URL+ResouceValues.swift\n//  CodeEdit\n//\n//  Created by Axel Martinez on 27/6/24.\n//\n\nimport Foundation\nimport UniformTypeIdentifiers\n\nextension URL {\n    fileprivate var resourceValues: URLResourceValues? {\n        try? self.resourceValues(forKeys: [.isDirectoryKey, .isSymbolicLinkKey, .contentTypeKey])\n    }\n\n    var isFolder: Bool {\n        resourceValues?.isDirectory ?? false\n    }\n\n    var isSymbolicLink: Bool {\n        resourceValues?.isSymbolicLink ?? false || (resourceValues?.contentType ?? .item) == .aliasFile\n    }\n\n    var contentType: UTType? {\n        resourceValues?.contentType\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/URL/URL+URLParameters.swift",
    "content": "//\n//  URL+URLParameters.swift\n//  CodeEditModules/GitAccounts\n//\n//  Created by Nanashi Li on 2022/03/31.\n//\nimport Foundation\n\nextension URL {\n\n    var URLParameters: [String: String] {\n        guard let components = URLComponents(url: self, resolvingAgainstBaseURL: false) else { return [:] }\n        var params = [String: String]()\n        components.queryItems?.forEach { queryItem in\n            params[queryItem.name] = queryItem.value\n        }\n        return params\n    }\n\n    func bitbucketURLParameters() -> [String: String] {\n            let stringParams = absoluteString.components(separatedBy: \"?\").last\n            let params = stringParams?.components(separatedBy: \"&\")\n            var returnParams: [String: String] = [:]\n            if let params {\n                for param in params {\n                    let keyValue = param.components(separatedBy: \"=\")\n                    if let key = keyValue.first, let value = keyValue.last {\n                        returnParams[key] = value\n                    }\n                }\n            }\n            return returnParams\n        }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/URL/URL+absolutePath.swift",
    "content": "//\n//  URL+LanguageServer.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 9/8/24.\n//\n\nimport Foundation\n\nextension URL {\n    var absolutePath: String {\n        absoluteURL.path(percentEncoded: false)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/URL/URL+componentCompare.swift",
    "content": "//\n//  URL+componentCompare.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 10/22/24.\n//\n\nimport Foundation\n\nextension URL {\n    /// Compare a URL using its path components.\n    /// - Parameter other: The URL to compare to\n    /// - Returns: `true` if the URL points to the same path on disk. Regardless of query parameters, trailing\n    ///            slashes, etc.\n    func componentCompare(_ other: URL) -> Bool {\n        return self.pathComponents == other.pathComponents\n    }\n\n    /// Determines if another URL is lower in the file system than this URL.\n    ///\n    /// Examples:\n    /// ```\n    /// URL(filePath: \"/Users/Bob/Desktop\").containsSubPath(URL(filePath: \"/Users/Bob/Desktop/file.txt\")) // true\n    /// URL(filePath: \"/Users/Bob/Desktop\").containsSubPath(URL(filePath: \"/Users/Bob/Desktop/\")) // false\n    /// URL(filePath: \"/Users/Bob/Desktop\").containsSubPath(URL(filePath: \"/Users/Bob/\")) // false\n    /// URL(filePath: \"/Users/Bob/Desktop\").containsSubPath(URL(filePath: \"/Users/Bob/Desktop/Folder\")) // true\n    /// ```\n    ///\n    /// - Parameter other: The URL to compare.\n    /// - Returns: True, if the other URL is lower in the file system.\n    func containsSubPath(_ other: URL) -> Bool {\n        other.absoluteString.starts(with: absoluteString)\n        && other.pathComponents.count > pathComponents.count\n    }\n\n    /// Compares this url with another, counting the number of shared path components. Stops counting once a\n    /// different component is found.\n    ///\n    /// - Note: URL treats a leading `/` as a component, so `/Users` and `/` will return `1`.\n    /// - Parameter other: The URL to compare against.\n    /// - Returns: The number of shared components.\n    func sharedComponents(_ other: URL) -> Int {\n        var count = 0\n        for (component, otherComponent) in zip(pathComponents, other.pathComponents) {\n            if component == otherComponent {\n                count += 1\n            } else {\n                return count\n            }\n        }\n        return count\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/View/View+focusedValue.swift",
    "content": "//\n//  View+focusedValue.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 18/06/2023.\n//\n\nimport SwiftUI\n\nextension View {\n    func focusedValue<Value>(\n        _ keyPath: WritableKeyPath<FocusedValues, Value?>,\n        disabled: Bool,\n        _ value: Value\n    ) -> some View {\n        focusedValue(keyPath, disabled ? nil : value)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/View/View+if.swift",
    "content": "//\n//  View+if.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 8/28/25.\n//\n\nimport SwiftUI\n\nextension View {\n    /// Applies the given transform if the given condition evaluates to `true`.\n    /// - Parameters:\n    ///   - condition: The condition to evaluate.\n    ///   - transform: The transform to apply to the source `View`.\n    /// - Returns: Either the original `View` or the modified `View` if the condition is `true`.\n    @ViewBuilder\n    func `if`<Content: View>(_ condition: Bool, @ViewBuilder transform: (Self) -> Content) -> some View {\n        if condition {\n            transform(self)\n        } else {\n            self\n        }\n    }\n\n    /// Applies the given transform if the given condition evaluates to `true`.\n    /// - Parameters:\n    ///   - condition: The condition to evaluate.\n    ///   - transform: The transform to apply to the source `View`.\n    /// - Returns: Either the original `View` or the modified `View` if the condition is `true`.\n    @ViewBuilder\n    func `if`<Content: View, ElseContent: View>(\n        _ condition: Bool,\n        @ViewBuilder transform: (Self) -> Content,\n        @ViewBuilder else elseTransform: (Self) -> ElseContent\n    ) -> some View {\n        if condition {\n            transform(self)\n        } else {\n            elseTransform(self)\n        }\n    }\n}\n\nextension Bool {\n     static var tahoe: Bool {\n         if #available(macOS 26, *) {\n             return true\n         } else {\n             return false\n         }\n     }\n }\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/View/View+isHovering.swift",
    "content": "//\n//  View+isHovering.swift\n//  CodeEditModules/StatusBar\n//\n//  Created by Lukas Pistrol on 22.03.22.\n//\n\nimport SwiftUI\n\nextension View {\n\n    /// Changes the cursor appearance when hovering attached View\n    /// - Parameters:\n    ///   - active: onHover() value\n    ///   - isDragging: indicate that dragging is happening. If true this will not change the cursor.\n    ///   - cursor: the cursor to display on hover\n    func isHovering(_ active: Bool, isDragging: Bool = false, cursor: NSCursor = .arrow) {\n        if isDragging { return }\n        if active {\n            cursor.push()\n        } else {\n            NSCursor.pop()\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Extensions/ZipFoundation/ZipFoundation+ErrorDescrioption.swift",
    "content": "//\n//  ZipFoundation+ErrorDescrioption.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 8/14/25.\n//\n\nimport Foundation\nimport ZIPFoundation\n\nextension Archive.ArchiveError: @retroactive LocalizedError {\n    public var errorDescription: String? {\n        switch self {\n        case .unreadableArchive:\n            \"Unreadable archive.\"\n        case .unwritableArchive:\n            \"Unwritable archive.\"\n        case .invalidEntryPath:\n            \"Invalid entry path.\"\n        case .invalidCompressionMethod:\n            \"Invalid compression method.\"\n        case .invalidCRC32:\n            \"Invalid checksum.\"\n        case .cancelledOperation:\n            \"Operation cancelled.\"\n        case .invalidBufferSize:\n            \"Invalid buffer size.\"\n        case .invalidEntrySize:\n            \"Invalid entry size.\"\n        case .invalidLocalHeaderDataOffset,\n                .invalidLocalHeaderSize,\n                .invalidCentralDirectoryOffset,\n                .invalidCentralDirectorySize,\n                .invalidCentralDirectoryEntryCount,\n                .missingEndOfCentralDirectoryRecord:\n            \"Invalid file detected.\"\n        case .uncontainedSymlink:\n            \"Uncontained symlink detected.\"\n        }\n    }\n\n    public var failureReason: String? {\n        return switch self {\n        case .invalidLocalHeaderDataOffset:\n            \"Invalid local header data offset.\"\n        case .invalidLocalHeaderSize:\n            \"Invalid local header size.\"\n        case .invalidCentralDirectoryOffset:\n            \"Invalid central directory offset.\"\n        case .invalidCentralDirectorySize:\n            \"Invalid central directory size.\"\n        case .invalidCentralDirectoryEntryCount:\n            \"Invalid central directory entry count.\"\n        case .missingEndOfCentralDirectoryRecord:\n            \"Missing end of central directory record.\"\n        default:\n            nil\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/FocusedValues.swift",
    "content": "//\n//  FocusedValues.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 18/06/2023.\n//\n\nimport SwiftUI\n\nextension FocusedValues {\n    var navigationSplitViewVisibility: Binding<NavigationSplitViewVisibility>? {\n        get { self[NavSplitViewVisibilityFocusedValueKey.self] }\n        set { self[NavSplitViewVisibilityFocusedValueKey.self] = newValue }\n    }\n\n    var inspectorVisibility: Binding<Bool>? {\n        get { self[InspectorVisibilityFocusedValueKey.self] }\n        set { self[InspectorVisibilityFocusedValueKey.self] = newValue }\n    }\n\n    private struct NavSplitViewVisibilityFocusedValueKey: FocusedValueKey {\n        typealias Value = Binding<NavigationSplitViewVisibility>\n    }\n\n    private struct InspectorVisibilityFocusedValueKey: FocusedValueKey {\n        typealias Value = Binding<Bool>\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Formatters/RegexFormatter.swift",
    "content": "//\n//  RegexFormatter.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 7/28/24.\n//\n\nimport SwiftUI\n\nclass RegexFormatter: Formatter {\n    let regex: NSRegularExpression\n    let replacementTemplate: String\n\n    init(pattern: String, replacementTemplate: String = \"\") {\n        do {\n            self.regex = try NSRegularExpression(pattern: pattern, options: [])\n        } catch {\n            fatalError(\"Invalid regex pattern\")\n        }\n        self.replacementTemplate = replacementTemplate\n        super.init()\n    }\n\n    required init?(coder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n    override func string(for obj: Any?) -> String? {\n        guard let string = obj as? String else { return nil }\n        return formatString(string)\n    }\n\n    override func getObjectValue(\n        _ obj: AutoreleasingUnsafeMutablePointer<AnyObject?>?,\n        for string: String,\n        errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?\n    ) -> Bool {\n        obj?.pointee = formatString(string) as NSString\n        return true\n    }\n\n    override func isPartialStringValid(\n        _ partialString: String,\n        newEditingString: AutoreleasingUnsafeMutablePointer<NSString?>?,\n        errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?\n    ) -> Bool {\n        let formatted = formatString(partialString)\n        newEditingString?.pointee = formatted as NSString\n        return formatted == partialString\n    }\n\n    private func formatString(_ string: String) -> String {\n        let range = NSRange(location: 0, length: string.utf16.count)\n        return regex.stringByReplacingMatches(in: string, options: [], range: range, withTemplate: replacementTemplate)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Formatters/TrimWhitespaceFormatter.swift",
    "content": "//\n//  TrimWhitespaceFormatter.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 7/28/24.\n//\n\nimport SwiftUI\n\nclass TrimWhitespaceFormatter: Formatter {\n    override func string(for obj: Any?) -> String? {\n        guard let string = obj as? String else { return nil }\n        return string.trimmingCharacters(in: .whitespacesAndNewlines)\n    }\n\n    override func getObjectValue(\n        _ obj: AutoreleasingUnsafeMutablePointer<AnyObject?>?,\n        for string: String,\n        errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?\n    ) -> Bool {\n        obj?.pointee = string.trimmingCharacters(in: .whitespacesAndNewlines) as NSString\n        return true\n    }\n\n    override func isPartialStringValid(\n        _ partialString: String,\n        newEditingString: AutoreleasingUnsafeMutablePointer<NSString?>?,\n        errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?\n    ) -> Bool {\n        let trimmed = partialString.trimmingCharacters(in: .whitespacesAndNewlines)\n        newEditingString?.pointee = trimmed as NSString\n        return true\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/KeyChain/CodeEditKeychain.swift",
    "content": "//\n//  CodeEditKeychain.swift\n//  CodeEditModules/CodeEditUtils\n//\n//  Created by Nanashi Li on 2022/04/14.\n//\n\nimport Foundation\nimport Security\n\n// TODO: DOCS (Nanashi Li)\nclass CodeEditKeychain {\n\n    var lastQueryParameters: [String: Any]? // Used by the unit tests\n\n    /// Contains result code from the last operation. Value is noErr (0) for a successful result.\n    var lastResultCode: OSStatus = noErr\n\n    var keyPrefix = \"\" // Can be useful in test.\n\n    /**\n     Specify an access group that will be used to access keychain items.\n     Access groups can be used to share keychain items between applications.\n     When access group value is nil all application access groups are being accessed.\n     Access group name is used by all functions: set, get, delete and clear.\n     */\n    var accessGroup: String?\n\n    private let lock = NSLock()\n\n    init() { }\n\n    /**\n     - parameter keyPrefix: a prefix that is added before the key in get/set methods.\n     Note that `clear` method still clears everything from the Keychain.\n     */\n    init(keyPrefix: String) {\n        self.keyPrefix = keyPrefix\n    }\n\n    /**\n     Stores the text value in the keychain item under the given key.\n     - parameter key: Key under which the text value is stored in the keychain.\n     - parameter value: Text string to be written to the keychain.\n     - parameter withAccess: Value that indicates when your app needs access to the text in the keychain item.\n     By default the .AccessibleWhenUnlocked option is used that permits the data to be accessed only\n     while the device is unlocked by the user.\n     - returns: True if the text was successfully written to the keychain.\n     */\n    @discardableResult\n    func set(\n        _ value: String,\n        forKey key: String,\n        withAccess access: CodeEditKeychainAccessOptions? = nil\n    ) -> Bool {\n        if let value = value.data(using: String.Encoding.utf8) {\n            return set(value, forKey: key, withAccess: access)\n        }\n        return false\n    }\n\n    /**\n     Stores the data in the keychain item under the given key.\n     - parameter key: Key under which the data is stored in the keychain.\n     - parameter value: Data to be written to the keychain.\n     - parameter withAccess: Value that indicates when your app needs access to the text in the keychain item.\n     By default the .AccessibleWhenUnlocked option is used that permits the data to be accessed\n     only while the device is unlocked by the user.\n     - returns: True if the text was successfully written to the keychain.\n     */\n    @discardableResult\n    func set(\n        _ value: Data,\n        forKey key: String,\n        withAccess access: CodeEditKeychainAccessOptions? = nil\n    ) -> Bool {\n        // The lock prevents the code to be run simultaneously\n        // from multiple threads which may result in crashing\n        lock.lock()\n        defer { lock.unlock() }\n\n        deleteNoLock(key) // Delete any existing key before saving it\n        let accessible = access?.value ?? CodeEditKeychainAccessOptions.defaultOption.value\n\n        let prefixedKey = keyWithPrefix(key)\n\n        var query: [String: Any] = [\n            CodeEditKeychainConstants.class: kSecClassGenericPassword,\n            CodeEditKeychainConstants.attrAccount: prefixedKey,\n            CodeEditKeychainConstants.valueData: value,\n            CodeEditKeychainConstants.accessible: accessible\n        ]\n\n        query = addAccessGroupWhenPresent(query)\n        lastQueryParameters = query\n\n        lastResultCode = SecItemAdd(query as CFDictionary, nil)\n\n        return lastResultCode == noErr\n    }\n\n    /**\n     Stores the boolean value in the keychain item under the given key.\n     - parameter key: Key under which the value is stored in the keychain.\n     - parameter value: Boolean to be written to the keychain.\n     - parameter withAccess: Value that indicates when your app needs access to the value in the keychain item.\n     By default the .AccessibleWhenUnlocked option is used that permits the data to be accessed\n     only while the device is unlocked by the user.\n     - returns: True if the value was successfully written to the keychain.\n     */\n    @discardableResult\n    func set(\n        _ value: Bool,\n        forKey key: String,\n        withAccess access: CodeEditKeychainAccessOptions? = nil\n    ) -> Bool {\n        let bytes: [UInt8] = value ? [1] : [0]\n        let data = Data(bytes)\n\n        return set(data, forKey: key, withAccess: access)\n    }\n\n    /**\n     Retrieves the text value from the keychain that corresponds to the given key.\n     - parameter key: The key that is used to read the keychain item.\n     - returns: The text value from the keychain. Returns nil if unable to read the item.\n     */\n    func get(_ key: String) -> String? {\n        if let data = getData(key) {\n            // Use the optional version here\n            if let currentString = String(data: data, encoding: .utf8) {\n                return currentString\n            }\n\n            lastResultCode = -67853 // errSecInvalidEncoding\n        }\n\n        return nil\n    }\n\n    /**\n     Retrieves the data from the keychain that corresponds to the given key.\n     - parameter key: The key that is used to read the keychain item.\n     - parameter asReference: If true, returns the data as reference (needed for things like NEVPNProtocol).\n     - returns: The text value from the keychain. Returns nil if unable to read the item.\n     */\n    func getData(_ key: String, asReference: Bool = false) -> Data? {\n        // The lock prevents the code to be run simultaneously\n        // from multiple threads which may result in crashing\n        lock.lock()\n        defer { lock.unlock() }\n\n        let prefixedKey = keyWithPrefix(key)\n\n        var query: [String: Any] = [\n            CodeEditKeychainConstants.class: kSecClassGenericPassword,\n            CodeEditKeychainConstants.attrAccount: prefixedKey,\n            CodeEditKeychainConstants.matchLimit: kSecMatchLimitOne\n        ]\n\n        if asReference {\n            query[CodeEditKeychainConstants.returnReference] = kCFBooleanTrue\n        } else {\n            query[CodeEditKeychainConstants.returnData] =  kCFBooleanTrue\n        }\n\n        query = addAccessGroupWhenPresent(query)\n        lastQueryParameters = query\n\n        var result: AnyObject?\n\n        lastResultCode = withUnsafeMutablePointer(to: &result) {\n            SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))\n        }\n\n        if lastResultCode == noErr {\n            return result as? Data\n        }\n\n        return nil\n    }\n\n    /**\n     Retrieves the boolean value from the keychain that corresponds to the given key.\n     - parameter key: The key that is used to read the keychain item.\n     - returns: The boolean value from the keychain. Returns nil if unable to read the item.\n     */\n    func getBool(_ key: String) -> Bool? {\n        guard let data = getData(key) else { return nil }\n        guard let firstBit = data.first else { return nil }\n        return firstBit == 1\n    }\n\n    /**\n     Deletes the single keychain item specified by the key.\n     - parameter key: The key that is used to delete the keychain item.\n     - returns: True if the item was successfully deleted.\n     */\n    @discardableResult\n    func delete(_ key: String) -> Bool {\n        // The lock prevents the code to be run simultaneously\n        // from multiple threads which may result in crashing\n        lock.lock()\n        defer { lock.unlock() }\n\n        return deleteNoLock(key)\n    }\n\n    /**\n     Return all keys from keychain\n     - returns: An string array with all keys from the keychain.\n     */\n    var allKeys: [String] {\n        var query: [String: Any] = [\n            CodeEditKeychainConstants.class: kSecClassGenericPassword,\n            CodeEditKeychainConstants.returnData: true,\n            CodeEditKeychainConstants.returnAttributes: true,\n            CodeEditKeychainConstants.returnReference: true,\n            CodeEditKeychainConstants.matchLimit: CodeEditKeychainConstants.secMatchLimitAll\n        ]\n\n        query = addAccessGroupWhenPresent(query)\n\n        var result: AnyObject?\n\n        let lastResultCode = withUnsafeMutablePointer(to: &result) {\n            SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))\n        }\n\n        if lastResultCode == noErr {\n            return (result as? [[String: Any]])?.compactMap {\n                $0[CodeEditKeychainConstants.attrAccount] as? String } ?? []\n        }\n\n        return []\n    }\n\n    /**\n     Same as `delete` but is only accessed internally, since it is not thread safe.\n     - parameter key: The key that is used to delete the keychain item.\n     - returns: True if the item was successfully deleted.\n     */\n    @discardableResult\n    func deleteNoLock(_ key: String) -> Bool {\n        let prefixedKey = keyWithPrefix(key)\n\n        var query: [String: Any] = [\n            CodeEditKeychainConstants.class: kSecClassGenericPassword,\n            CodeEditKeychainConstants.attrAccount: prefixedKey\n        ]\n\n        query = addAccessGroupWhenPresent(query)\n        lastQueryParameters = query\n\n        lastResultCode = SecItemDelete(query as CFDictionary)\n\n        return lastResultCode == noErr\n    }\n\n    /**\n     Deletes all Keychain items used by the app.\n     Note that this method deletes all items regardless of the prefix settings used for initializing the class.\n     - returns: True if the keychain items were successfully deleted.\n     */\n    @discardableResult\n    func clear() -> Bool {\n        // The lock prevents the code to be run simultaneously\n        // from multiple threads which may result in crashing\n        lock.lock()\n        defer { lock.unlock() }\n\n        var query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword ]\n        query = addAccessGroupWhenPresent(query)\n        lastQueryParameters = query\n\n        lastResultCode = SecItemDelete(query as CFDictionary)\n\n        return lastResultCode == noErr\n    }\n\n    /// Returns the key with currently set prefix.\n    func keyWithPrefix(_ key: String) -> String {\n        \"\\(keyPrefix)\\(key)\"\n    }\n\n    func addAccessGroupWhenPresent(_ items: [String: Any]) -> [String: Any] {\n        guard let accessGroup else { return items }\n\n        var result: [String: Any] = items\n        result[CodeEditKeychainConstants.accessGroup] = accessGroup\n        return result\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/KeyChain/CodeEditKeychainConstants.swift",
    "content": "//\n//  CodeEditKeychainConstants.swift\n//  CodeEditModules/CodeEditUtils\n//\n//  Created by Nanashi Li on 2022/04/14.\n//\n\nimport Foundation\nimport Security\n\n/// Constants used by the library\nenum CodeEditKeychainConstants {\n    /// Specifies a Keychain access group. Used for sharing Keychain items between apps.\n    static var accessGroup: String { toString(kSecAttrAccessGroup) }\n\n    /**\n     A value that indicates when your app needs access to the data in a keychain item.\n     The default value is AccessibleWhenUnlocked.\n     For a list of possible values, see CodeEditKeychainAccessOptions.\n     */\n    static var accessible: String { toString(kSecAttrAccessible) }\n\n    /// Used for specifying a String key when setting/getting a Keychain value.\n    static var attrAccount: String { toString(kSecAttrAccount) }\n\n    /// Used for specifying synchronization of keychain items between devices.\n    static var attrSynchronizable: String { toString(kSecAttrSynchronizable) }\n\n    /// An item class key used to construct a Keychain search dictionary.\n    static var `class`: String { toString(kSecClass) }\n\n    /// Specifies the number of values returned from the keychain. The library only supports single values.\n    static var matchLimit: String { toString(kSecMatchLimit) }\n\n    /// A return data type used to get the data from the Keychain.\n    static var returnData: String { toString(kSecReturnData) }\n\n    /// Used for specifying a value when setting a Keychain value.\n    static var valueData: String { toString(kSecValueData) }\n\n    /// Used for returning a reference to the data from the keychain\n    static var returnReference: String { toString(kSecReturnPersistentRef) }\n\n    /// A key whose value is a Boolean indicating whether or not to return item attributes\n    static var returnAttributes: String { toString(kSecReturnAttributes) }\n\n    /// A value that corresponds to matching an unlimited number of items\n    static var secMatchLimitAll: String { toString(kSecMatchLimitAll) }\n\n    static func toString(_ value: CFString) -> String {\n        value as String\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/KeyChain/KeychainSwiftAccessOptions.swift",
    "content": "//\n//  CodeEditKeychainAccessOptions.swift\n//  CodeEditModules/CodeEditUtils\n//\n//  Created by Nanashi Li on 2022/04/14.\n//\n\nimport Security\n\n/**\n These options are used to determine when a keychain item should be readable.\n The default value is AccessibleWhenUnlocked.\n */\nenum CodeEditKeychainAccessOptions {\n\n    /**\n     The data in the keychain item can be accessed only while the device is unlocked by the user.\n\n     This is recommended for items that need to be accessible only while the application is in the foreground.\n     Items with this attribute migrate to a new device when using encrypted backups.\n\n     This is the default value for keychain items added without explicitly setting an accessibility constant.\n     */\n    case accessibleWhenUnlocked\n\n    /**\n     The data in the keychain item can be accessed only while the device is unlocked by the user.\n\n     This is recommended for items that need to be accessible only while the application is in the foreground.\n     Items with this attribute do not migrate to a new device.\n     Thus, after restoring from a backup of a different device, these items will not be present.\n     */\n    case accessibleWhenUnlockedThisDeviceOnly\n\n    /**\n     The data in the keychain item cannot be accessed after a restart until the device has\n     been unlocked once by the user.\n\n     After the first unlock, the data remains accessible until the next restart.\n     This is recommended for items that need to be accessed by background applications.\n     Items with this attribute migrate to a new device when using encrypted backups.\n     */\n    case accessibleAfterFirstUnlock\n\n    /**\n     The data in the keychain item cannot be accessed after a restart until the device has\n     been unlocked once by the user.\n\n     After the first unlock, the data remains accessible until the next restart.\n     This is recommended for items that need to be accessed by background applications.\n     Items with this attribute do not migrate to a new device.\n     Thus, after restoring from a backup of a different device, these items will not be present.\n     */\n    case accessibleAfterFirstUnlockThisDeviceOnly\n\n    /**\n     The data in the keychain can only be accessed when the device is unlocked.\n     Only available if a passcode is set on the device.\n\n     This is recommended for items that only need to be accessible while the application is in the foreground.\n     Items with this attribute never migrate to a new device.\n     After a backup is restored to a new device, these items are missing.\n     No items can be stored in this class on devices without a passcode.\n     Disabling the device passcode causes all items in this class to be deleted.\n     */\n    case accessibleWhenPasscodeSetThisDeviceOnly\n\n    static var defaultOption: CodeEditKeychainAccessOptions {\n        .accessibleWhenUnlocked\n    }\n\n    var value: String {\n        switch self {\n        case .accessibleWhenUnlocked:\n            return toString(kSecAttrAccessibleWhenUnlocked)\n\n        case .accessibleWhenUnlockedThisDeviceOnly:\n            return toString(kSecAttrAccessibleWhenUnlockedThisDeviceOnly)\n\n        case .accessibleAfterFirstUnlock:\n            return toString(kSecAttrAccessibleAfterFirstUnlock)\n\n        case .accessibleAfterFirstUnlockThisDeviceOnly:\n            return toString(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly)\n\n        case .accessibleWhenPasscodeSetThisDeviceOnly:\n            return toString(kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly)\n        }\n    }\n\n    func toString(_ value: CFString) -> String {\n        CodeEditKeychainConstants.toString(value)\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Limiter.swift",
    "content": "//\n//  Limiter.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 11/1/24.\n//\n\nimport Combine\nimport Foundation\n\n// TODO: Look into improving this API by using async by default so `Task` isn't needed when used.\nenum Limiter {\n    // Keep track of debounce timers and throttle states\n    private static var debounceTimers: [AnyHashable: Timer] = [:]\n    private static var throttleLastExecution: [AnyHashable: Date] = [:]\n\n    /// Debounces an action with a specified duration and identifier.\n    /// - Parameters:\n    ///   - id: A unique identifier for the debounced action.\n    ///   - duration: The debounce duration in seconds.\n    ///   - action: The action to be executed after the debounce period.\n    static func debounce(id: AnyHashable, duration: TimeInterval, action: @escaping () -> Void) {\n        // Cancel any existing debounce timer for the given ID\n        debounceTimers[id]?.invalidate()\n        // Start a new debounce timer for the given ID\n        debounceTimers[id] = Timer.scheduledTimer(withTimeInterval: duration, repeats: false) { _ in\n            action()\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Protocols/Loopable.swift",
    "content": "//\n//  Loopable.swift\n//  CodeEditModules/Settings\n//\n//  Created by Lukas Pistrol on 03.04.22.\n//\n\nimport Foundation\n\n/// Loopable protocol implements a method that will return all child\n/// properties and their associated values of a `Type`\nprotocol Loopable {\n    func allProperties() throws -> [String: Any]\n}\n\nextension Loopable {\n\n    /// returns all child properties and their associated values of `self`\n    ///\n    /// **Example:**\n    /// ```swift\n    /// struct Author: Loopable {\n    ///   var name: String = \"Steve\"\n    ///   var books: Int = 4\n    /// }\n    ///\n    /// let author = Author()\n    /// print(author.allProperties())\n    ///\n    /// // returns\n    /// [\"name\": \"Steve\", \"books\": 4]\n    /// ```\n    func allProperties() throws -> [String: Any] {\n        var result: [String: Any] = [:]\n\n        let mirror = Mirror(reflecting: self)\n\n        guard let style = mirror.displayStyle, style == .struct || style == .class else {\n            // TODO: Throw a proper error\n            throw NSError() // swiftlint:disable:this discouraged_direct_init\n        }\n\n        for (property, value) in mirror.children {\n            guard let property else {\n                continue\n            }\n\n            result[property] = value\n        }\n\n        return result\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/Protocols/SearchableSettingsPage.swift",
    "content": "//\n//  SearchableSettingsPage.swift\n//  CodeEdit\n//\n//  Created by Raymond Vleeshouwer on 07/07/23.\n//\n\nimport Foundation\n\nprotocol SearchableSettingsPage {\n    var searchKeys: [String] { get }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/ShellClient/Models/ShellClient.swift",
    "content": "//\n//  ShellClient.swift\n//  CodeEdit\n//\n//  Created by Matthijs Eikelenboom on 25/11/2022.\n//\n\nimport Combine\nimport Foundation\n\n/// Errors that can occur during shell operations\nenum ShellClientError: Error {\n    case failedToDecodeOutput\n    case taskTerminated(code: Int)\n}\n\n/// Shell Client\n/// Run commands in shell\nclass ShellClient {\n    /// Generate a process and pipe to run commands\n    /// - Parameter args: commands to run\n    /// - Returns: command output\n    func generateProcessAndPipe(_ args: [String]) -> (Process, Pipe) {\n        // Run in an 'interactive' login shell. Because we're passing -c here it won't actually be\n        // interactive but it will source the user's zshrc file as well as the zshprofile.\n        var arguments = [\"-lic\"]\n        arguments.append(contentsOf: args)\n        let task = Process()\n        let pipe = Pipe()\n        task.standardOutput = pipe\n        task.standardError = pipe\n        task.arguments = arguments\n        task.executableURL = URL(fileURLWithPath: \"/bin/zsh\")\n        return (task, pipe)\n    }\n\n    /// Cancellable tasks\n    var cancellables: [UUID: AnyCancellable] = [:]\n\n    /// Run a command\n    /// - Parameter args: command to run\n    /// - Returns: command output\n    @discardableResult\n    func run(_ args: String...) throws -> String {\n        let (task, pipe) = generateProcessAndPipe(args)\n        try task.run()\n        let data = pipe.fileHandleForReading.readDataToEndOfFile()\n        guard let output = String(bytes: data, encoding: .utf8) else {\n            throw ShellClientError.failedToDecodeOutput\n        }\n        return output\n    }\n\n    /// Run a command with Publisher\n    /// - Parameter args: command to run\n    /// - Returns: command output\n    @discardableResult\n    func runLive(_ args: String...) -> AnyPublisher<String, Never> {\n        let subject = PassthroughSubject<String, Never>()\n        let (task, pipe) = generateProcessAndPipe(args)\n        let outputHandler = pipe.fileHandleForReading\n        // wait for the data to come in and then notify\n        // the Notification with Name: `NSFileHandleDataAvailable`\n        outputHandler.waitForDataInBackgroundAndNotify()\n        let id = UUID()\n        self.cancellables[id] = NotificationCenter\n            .default\n            .publisher(for: .NSFileHandleDataAvailable, object: outputHandler)\n            .sink { _ in\n                let data = outputHandler.availableData\n                guard !data.isEmpty else {\n                    // if no data is available anymore\n                    // we should cancel this cancellable\n                    // and mark the subject as finished\n                    self.cancellables.removeValue(forKey: id)\n                    subject.send(completion: .finished)\n                    return\n                }\n                guard let output = String(bytes: data, encoding: .utf8) else {\n                    subject.send(completion: .finished)\n                    return\n                }\n                output.split(whereSeparator: \\.isNewline)\n                    .forEach({ subject.send(String($0)) })\n                outputHandler.waitForDataInBackgroundAndNotify()\n            }\n        task.launch()\n        return subject.eraseToAnyPublisher()\n    }\n\n    /// Run a command with AsyncStream\n    /// - Parameter args: command to run\n    /// - Returns: async stream of command output\n    func runAsync(_ args: String...) -> AsyncThrowingStream<String, Error> {\n        let (task, pipe) = generateProcessAndPipe(args)\n\n        return AsyncThrowingStream { continuation in\n            pipe.fileHandleForReading.readabilityHandler = { [unowned pipe] fileHandle in\n                let data = fileHandle.availableData\n                if !data.isEmpty {\n                    guard let output = String(bytes: data, encoding: .utf8) else {\n                        continuation.finish(throwing: ShellClientError.failedToDecodeOutput)\n                        return\n                    }\n                    output.split(whereSeparator: \\.isNewline)\n                        .forEach({ continuation.yield(String($0)) })\n                } else {\n                    if !task.isRunning && task.terminationStatus != 0 {\n                        continuation.finish(\n                            throwing: ShellClientError.taskTerminated(code: Int(task.terminationStatus))\n                        )\n                    } else {\n                        continuation.finish()\n                    }\n\n                    // Clean up the handler to prevent repeated calls and continuation finishes for the same\n                    // process.\n                    pipe.fileHandleForReading.readabilityHandler = nil\n                }\n            }\n\n            do {\n                try task.run()\n            } catch {\n                continuation.finish(throwing: error)\n            }\n        }\n    }\n\n    /// Shell client\n    /// - Returns: description\n    static func live() -> ShellClient {\n        return ShellClient()\n    }\n}\n"
  },
  {
    "path": "CodeEdit/Utils/withTimeout.swift",
    "content": "//\n//  TimedOutError.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 7/8/25.\n//\n\nstruct TimedOutError: Error, Equatable {}\n\n/// Execute an operation in the current task subject to a timeout.\n/// - Warning: This still requires cooperative task cancellation to work correctly. Ensure tasks opt\n///            into cooperative cancellation.\n/// - Parameters:\n///   - duration: The duration to wait until timing out. Uses a continuous clock.\n///   - operation: The async operation to perform.\n/// - Returns: Returns the result of `operation` if it completed in time.\n/// - Throws: Throws ``TimedOutError`` if the timeout expires before `operation` completes.\n///   If `operation` throws an error before the timeout expires, that error is propagated to the caller.\npublic func withTimeout<R>(\n    duration: Duration,\n    onTimeout: @escaping @Sendable () async throws -> Void = { },\n    operation: @escaping @Sendable () async throws -> R\n) async throws -> R {\n    return try await withThrowingTaskGroup(of: R.self) { group in\n        let deadline: ContinuousClock.Instant = .now + duration\n\n        // Start actual work.\n        group.addTask {\n            return try await operation()\n        }\n        // Start timeout child task.\n        group.addTask {\n            if .now < deadline {\n                try await Task.sleep(until: deadline) // sleep until the deadline\n            }\n            try Task.checkCancellation()\n            // We’ve reached the timeout.\n            try await onTimeout()\n            throw TimedOutError()\n        }\n        // First finished child task wins, cancel the other task.\n        defer { group.cancelAll() }\n        do {\n            let result = try await group.next()!\n            return result\n        } catch {\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/WindowObserver.swift",
    "content": "//\n//  WindowObserver.swift\n//  CodeEdit\n//\n//  Created by Wouter Hennen on 14/01/2023.\n//\n\nimport SwiftUI\n\nstruct WindowObserver<Content: View>: View {\n\n    var window: WindowBox\n\n    @ViewBuilder var content: Content\n\n    /// The fullscreen state of the NSWindow.\n    /// This will be passed into all child views as an environment variable.\n    @State private var isFullscreen = false\n\n    @State var modifierFlags: NSEvent.ModifierFlags = []\n\n    var body: some View {\n        content\n            .environment(\\.modifierKeys, modifierFlags.intersection(.deviceIndependentFlagsMask))\n            .onReceive(NSEvent.publisher(scope: .local, matching: .flagsChanged)) { output in\n                modifierFlags = output.modifierFlags\n            }\n            .environment(\\.window, window)\n            .environment(\\.isFullscreen, isFullscreen)\n            .onReceive(NotificationCenter.default.publisher(for: NSWindow.didEnterFullScreenNotification)) { _ in\n                self.isFullscreen = true\n            }\n            .onReceive(NotificationCenter.default.publisher(for: NSWindow.willExitFullScreenNotification)) { _ in\n                self.isFullscreen = false\n            }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/WorkspaceSheets.swift",
    "content": "//\n//  WorkspaceSheets.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 7/1/24.\n//\n\nimport SwiftUI\n\nstruct WorkspaceSheets: View {\n    @EnvironmentObject var sourceControlManager: SourceControlManager\n\n    var body: some View {\n        EmptyView()\n            .sheet(isPresented: Binding<Bool>(\n                get: { sourceControlManager.pushSheetIsPresented &&\n                       !sourceControlManager.addExistingRemoteSheetIsPresented },\n                set: { sourceControlManager.pushSheetIsPresented = $0 }\n            )) {\n                SourceControlPushView()\n            }\n            .sheet(isPresented: Binding<Bool>(\n                get: { sourceControlManager.pullSheetIsPresented &&\n                       !sourceControlManager.addExistingRemoteSheetIsPresented &&\n                       !sourceControlManager.stashSheetIsPresented },\n                set: { sourceControlManager.pullSheetIsPresented = $0 }\n            )) {\n                if sourceControlManager.addExistingRemoteSheetIsPresented == true {\n                    SourceControlAddExistingRemoteView()\n                } else {\n                    SourceControlPullView()\n                }\n            }\n            .sheet(isPresented: $sourceControlManager.fetchSheetIsPresented) {\n                SourceControlFetchView()\n            }\n            .sheet(isPresented: $sourceControlManager.stashSheetIsPresented) {\n                SourceControlStashView()\n            }\n            .sheet(isPresented: $sourceControlManager.addExistingRemoteSheetIsPresented) {\n                SourceControlAddExistingRemoteView()\n            }\n            .sheet(item: Binding<GitBranch?>(\n                get: {\n                    sourceControlManager.switchToBranch != nil\n                    && sourceControlManager.stashSheetIsPresented\n                    ? nil\n                    : sourceControlManager.switchToBranch\n                },\n                set: { sourceControlManager.switchToBranch = $0 }\n            )) { branch in\n                SourceControlSwitchView(branch: branch)\n            }\n            .alert(isPresented: $sourceControlManager.discardAllAlertIsPresented) {\n                Alert(\n                    title: Text(\"Do you want to discard all uncommitted, local changes?\"),\n                    message: Text(\"This action cannot be undone.\"),\n                    primaryButton: .destructive(Text(\"Discard\")) {\n                        sourceControlManager.discardAllChanges()\n                    },\n                    secondaryButton: .cancel()\n                )\n            }\n            .alert(\"Cannot Stage Changes\", isPresented: $sourceControlManager.noChangesToStageAlertIsPresented) {\n                Button(\"OK\", role: .cancel) {}\n            } message: {\n                Text(\"There are no uncommitted changes in the local repository for this project.\")\n            }\n            .alert(\"Cannot Unstage Changes\", isPresented: $sourceControlManager.noChangesToUnstageAlertIsPresented) {\n                Button(\"OK\", role: .cancel) {}\n            } message: {\n                Text(\"There are no uncommitted changes in the local repository for this project.\")\n            }\n            .alert(\"Cannot Stash Changes\", isPresented: $sourceControlManager.noChangesToStashAlertIsPresented) {\n                Button(\"OK\", role: .cancel) {}\n            } message: {\n                Text(\"There are no uncommitted changes in the local repository for this project.\")\n            }\n            .alert(\"Cannot Discard Changes\", isPresented: $sourceControlManager.noChangesToDiscardAlertIsPresented) {\n                Button(\"OK\", role: .cancel) {}\n            } message: {\n                Text(\"There are no uncommitted changes in the local repository for this project.\")\n            }\n    }\n}\n"
  },
  {
    "path": "CodeEdit/WorkspaceView.swift",
    "content": "//\n//  WorkspaceView.swift\n//  CodeEdit\n//\n//  Created by Austin Condiff on 3/10/22.\n//\n\nimport SwiftUI\nimport UniformTypeIdentifiers\n\nstruct WorkspaceView: View {\n    @Environment(\\.window.value)\n    private var window: NSWindow?\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @FocusState var focusedEditor: Editor?\n\n    @AppSettings(\\.theme.matchAppearance)\n    var matchAppearance\n\n    @AppSettings(\\.sourceControl.general.sourceControlIsEnabled)\n    var sourceControlIsEnabled\n\n    @EnvironmentObject private var workspace: WorkspaceDocument\n    @EnvironmentObject private var editorManager: EditorManager\n    @EnvironmentObject private var utilityAreaViewModel: UtilityAreaViewModel\n\n    @StateObject private var themeModel: ThemeModel = .shared\n\n    @State private var showingAlert = false\n    @State private var terminalCollapsed = true\n    @State private var editorCollapsed = false\n    @State private var editorsHeight: CGFloat = 0\n    @State private var drawerHeight: CGFloat = 0\n\n    private let statusbarHeight: CGFloat = 29\n\n    private var keybindings: KeybindingManager =  .shared\n\n    var body: some View {\n        if workspace.workspaceFileManager != nil, let sourceControlManager = workspace.sourceControlManager {\n            VStack {\n                SplitViewReader { proxy in\n                    SplitView(axis: .vertical) {\n                        editorArea\n                        utilityAreaPlaceholder\n                    }\n                    .edgesIgnoringSafeArea(.top)\n                    .frame(maxWidth: .infinity, maxHeight: .infinity)\n                    .overlay(alignment: .top) {\n                        utilityArea(proxy: proxy)\n                    }\n                    .overlay(alignment: .topTrailing) {\n                        NotificationPanelView()\n                    }\n\n                    // MARK: - Tab Focus Listeners\n\n                    .onChange(of: editorManager.activeEditor) { _, newValue in\n                        focusedEditor = newValue\n                    }\n                    .onChange(of: focusedEditor) { _, newValue in\n                        /// Update active tab group only if the new one is not the same with it.\n                        if let newValue, editorManager.activeEditor != newValue {\n                            editorManager.activeEditor = newValue\n                        }\n                    }\n\n                    // MARK: - Theme Color Scheme\n\n                    .task {\n                        themeModel.colorScheme = colorScheme\n                    }\n                    .onChange(of: colorScheme) { _, newValue in\n                        themeModel.colorScheme = newValue\n                        if matchAppearance {\n                            themeModel.selectedTheme = newValue == .dark\n                            ? themeModel.selectedDarkTheme\n                            : themeModel.selectedLightTheme\n                        }\n                    }\n\n                    // MARK: - Source Control\n\n                    .task {\n                        // Only refresh git data if source control is enabled\n                        guard sourceControlIsEnabled else { return }\n                        \n                        do {\n                            try await sourceControlManager.refreshRemotes()\n                            try await sourceControlManager.refreshStashEntries()\n                        } catch {\n                            await sourceControlManager.showAlertForError(\n                                title: \"Error refreshing Git data\",\n                                error: error\n                            )\n                        }\n                    }\n                    .onChange(of: sourceControlIsEnabled) { _, newValue in\n                        if newValue {\n                            Task {\n                                await sourceControlManager.refreshCurrentBranch()\n                            }\n                        } else {\n                            sourceControlManager.currentBranch = nil\n                        }\n                    }\n\n                    // MARK: - Window Will Close\n\n                    .onReceive(NotificationCenter.default.publisher(for: NSWindow.willCloseNotification)) { output in\n                        if let window = output.object as? NSWindow, self.window == window {\n                            workspace.addToWorkspaceState(\n                                key: .workspaceWindowSize,\n                                value: NSStringFromRect(window.frame)\n                            )\n                        }\n                    }\n                }\n            }\n            .background(EffectView(.contentBackground))\n            .background(WorkspaceSheets().environmentObject(sourceControlManager))\n            .onDrop(of: [.fileURL], isTargeted: nil) { providers in\n                _ = handleDrop(providers: providers)\n                return true\n            }\n            .accessibilityElement(children: .contain)\n            .accessibilityLabel(\"workspace area\")\n        }\n    }\n\n    // MARK: - Editor Area\n\n    @ViewBuilder private var editorArea: some View {\n        ZStack {\n            GeometryReader { geo in\n                EditorLayoutView(\n                    layout: editorManager.isFocusingActiveEditor\n                    ? editorManager.activeEditor.getEditorLayout() ?? editorManager.editorLayout\n                    : editorManager.editorLayout,\n                    focus: $focusedEditor\n                )\n                .frame(maxWidth: .infinity, maxHeight: .infinity)\n                .onChange(of: geo.size.height) { _, newHeight in\n                    editorsHeight = newHeight\n                }\n                .onAppear {\n                    editorsHeight = geo.size.height\n                }\n            }\n        }\n        .frame(minHeight: 170 + 29 + 29)\n        .collapsable()\n        .collapsed($utilityAreaViewModel.isMaximized)\n        .holdingPriority(.init(1))\n    }\n\n    // MARK: - Utility Area\n\n    @ViewBuilder\n    private func utilityArea(proxy: SplitViewProxy) -> some View {\n        ZStack(alignment: .top) {\n            UtilityAreaView()\n                .frame(height: utilityAreaViewModel.isMaximized ? nil : drawerHeight)\n                .frame(maxHeight: utilityAreaViewModel.isMaximized ? .infinity : nil)\n                .padding(.top, utilityAreaViewModel.isMaximized ? statusbarHeight + 1 : 0)\n                .offset(y: utilityAreaViewModel.isMaximized ? 0 : editorsHeight + 1)\n            VStack(spacing: 0) {\n                StatusBarView(proxy: proxy)\n                if utilityAreaViewModel.isMaximized {\n                    PanelDivider()\n                }\n            }\n            .offset(y: utilityAreaViewModel.isMaximized ? 0 : editorsHeight - statusbarHeight)\n        }\n        .accessibilityElement(children: .contain)\n    }\n\n    @ViewBuilder private var utilityAreaPlaceholder: some View {\n        Rectangle()\n            .collapsable()\n            .collapsed($utilityAreaViewModel.isCollapsed)\n            .splitViewCanAnimate($utilityAreaViewModel.animateCollapse)\n            .opacity(0)\n            .frame(idealHeight: 260)\n            .frame(minHeight: 100)\n            .background {\n                GeometryReader { geo in\n                    Rectangle()\n                        .opacity(0)\n                        .onChange(of: geo.size.height) { _, newHeight in\n                            drawerHeight = newHeight\n                        }\n                        .onAppear {\n                            drawerHeight = geo.size.height\n                        }\n                }\n            }\n            .accessibilityHidden(true)\n    }\n\n    private func handleDrop(providers: [NSItemProvider]) -> Bool {\n        for provider in providers {\n            provider.loadItem(forTypeIdentifier: UTType.fileURL.identifier, options: nil) { item, _ in\n                guard let data = item as? Data,\n                      let url = URL(dataRepresentation: data, relativeTo: nil) else {\n                    return\n                }\n\n                DispatchQueue.main.async {\n                    let file = CEWorkspaceFile(url: url)\n                    editorManager.activeEditor.openTab(file: file)\n                }\n            }\n        }\n        return true\n    }\n}\n"
  },
  {
    "path": "CodeEdit/World.swift",
    "content": "var currentWorld: World = .init()\n\n// Inspired by: https://vimeo.com/291588126\nstruct World {\n    var shellClient: ShellClient = .live()\n}\n"
  },
  {
    "path": "CodeEdit.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 73;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t2816F594280CF50500DD548B /* CodeEditSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = 2816F593280CF50500DD548B /* CodeEditSymbols */; };\n\t\t283BDCBD2972EEBD002AFF81 /* Package.resolved in Resources */ = {isa = PBXBuildFile; fileRef = 283BDCBC2972EEBD002AFF81 /* Package.resolved */; };\n\t\t284DC8512978BA2600BF2770 /* .all-contributorsrc in Resources */ = {isa = PBXBuildFile; fileRef = 284DC8502978BA2600BF2770 /* .all-contributorsrc */; };\n\t\t2BE487F428245162003F3F64 /* OpenWithCodeEdit.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 2BE487EC28245162003F3F64 /* OpenWithCodeEdit.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };\n\t\t302AD7FF2D8054D500231E16 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 30818CB42D4E563900967860 /* ZIPFoundation */; };\n\t\t30CB64912C16CA8100CC8A9E /* LanguageServerProtocol in Frameworks */ = {isa = PBXBuildFile; productRef = 30CB64902C16CA8100CC8A9E /* LanguageServerProtocol */; };\n\t\t30CB64942C16CA9100CC8A9E /* LanguageClient in Frameworks */ = {isa = PBXBuildFile; productRef = 30CB64932C16CA9100CC8A9E /* LanguageClient */; };\n\t\t583E529C29361BAB001AB554 /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 583E529B29361BAB001AB554 /* SnapshotTesting */; };\n\t\t58F2EB03292FB2B0004A9BDE /* Documentation.docc in Sources */ = {isa = PBXBuildFile; fileRef = 58F2EACE292FB2B0004A9BDE /* Documentation.docc */; };\n\t\t58F2EB1E292FB954004A9BDE /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 58F2EB1D292FB954004A9BDE /* Sparkle */; };\n\t\t5E4485612DF600D9008BBE69 /* AboutWindow in Frameworks */ = {isa = PBXBuildFile; productRef = 5E4485602DF600D9008BBE69 /* AboutWindow */; };\n\t\t5EACE6222DF4BF08005E08B8 /* WelcomeWindow in Frameworks */ = {isa = PBXBuildFile; productRef = 5EACE6212DF4BF08005E08B8 /* WelcomeWindow */; };\n\t\t6C0617D62BDB4432008C9C42 /* LogStream in Frameworks */ = {isa = PBXBuildFile; productRef = 6C0617D52BDB4432008C9C42 /* LogStream */; };\n\t\t6C0824A12C5C0C9700A0751E /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = 6C0824A02C5C0C9700A0751E /* SwiftTerm */; };\n\t\t6C147C4529A329350089B630 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = 6C147C4429A329350089B630 /* OrderedCollections */; };\n\t\t6C315FC82E05E33D0011BFC5 /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 6C315FC72E05E33D0011BFC5 /* CodeEditSourceEditor */; };\n\t\t6C66C31329D05CDC00DE9ED2 /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = 6C66C31229D05CDC00DE9ED2 /* GRDB */; };\n\t\t6C6BD6F429CD142C00235D17 /* CollectionConcurrencyKit in Frameworks */ = {isa = PBXBuildFile; productRef = 6C6BD6F329CD142C00235D17 /* CollectionConcurrencyKit */; };\n\t\t6C6BD6F829CD14D100235D17 /* CodeEditKit in Frameworks */ = {isa = PBXBuildFile; productRef = 6C6BD6F729CD14D100235D17 /* CodeEditKit */; };\n\t\t6C6BD6F929CD14D100235D17 /* CodeEditKit in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 6C6BD6F729CD14D100235D17 /* CodeEditKit */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n\t\t6C73A6D32D4F1E550012D95C /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 6C73A6D22D4F1E550012D95C /* CodeEditSourceEditor */; };\n\t\t6C76D6D42E15B91E00EF52C3 /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 6C76D6D32E15B91E00EF52C3 /* CodeEditSourceEditor */; };\n\t\t6C81916B29B41DD300B75C92 /* DequeModule in Frameworks */ = {isa = PBXBuildFile; productRef = 6C81916A29B41DD300B75C92 /* DequeModule */; };\n\t\t6C85BB402C2105ED00EB5DEF /* CodeEditKit in Frameworks */ = {isa = PBXBuildFile; productRef = 6C85BB3F2C2105ED00EB5DEF /* CodeEditKit */; };\n\t\t6C85BB442C210EFD00EB5DEF /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = 6C85BB432C210EFD00EB5DEF /* SwiftUIIntrospect */; };\n\t\t6C9DB9E42D55656300ACD86E /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 6C9DB9E32D55656300ACD86E /* CodeEditSourceEditor */; };\n\t\t6CAAF68A29BC9C2300A1F48A /* (null) in Sources */ = {isa = PBXBuildFile; };\n\t\t6CAAF69229BCC71C00A1F48A /* (null) in Sources */ = {isa = PBXBuildFile; };\n\t\t6CAAF69429BCD78600A1F48A /* (null) in Sources */ = {isa = PBXBuildFile; };\n\t\t6CB446402B6DFF3A00539ED0 /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 6CB4463F2B6DFF3A00539ED0 /* CodeEditSourceEditor */; };\n\t\t6CB9144B29BEC7F100BC47F2 /* (null) in Sources */ = {isa = PBXBuildFile; };\n\t\t6CB94D032CA1205100E8651C /* AsyncAlgorithms in Frameworks */ = {isa = PBXBuildFile; productRef = 6CB94D022CA1205100E8651C /* AsyncAlgorithms */; };\n\t\t6CC00A8B2CBEF150004E8134 /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 6CC00A8A2CBEF150004E8134 /* CodeEditSourceEditor */; };\n\t\t6CC17B4F2C432AE000834E2C /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 6CC17B4E2C432AE000834E2C /* CodeEditSourceEditor */; };\n\t\t6CCF6DD32E26D48F00B94F75 /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = 6CCF6DD22E26D48F00B94F75 /* SwiftTerm */; };\n\t\t6CCF73D02E26DE3200B94F75 /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = 6CCF73CF2E26DE3200B94F75 /* SwiftTerm */; };\n\t\t6CD3CA552C8B508200D83DCD /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 6CD3CA542C8B508200D83DCD /* CodeEditSourceEditor */; };\n\t\t6CE21E872C650D2C0031B056 /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = 6CE21E862C650D2C0031B056 /* SwiftTerm */; };\n\t\tB6FF04782B6C08AC002C2C78 /* DefaultThemes in Resources */ = {isa = PBXBuildFile; fileRef = B6FF04772B6C08AC002C2C78 /* DefaultThemes */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXContainerItemProxy section */\n\t\t2BE487F228245162003F3F64 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = B658FB2427DA9E0F00EA4DBD /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 2BE487EB28245162003F3F64;\n\t\t\tremoteInfo = OpenWithCodeEdit;\n\t\t};\n\t\tB658FB3E27DA9E1000EA4DBD /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = B658FB2427DA9E0F00EA4DBD /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = B658FB2B27DA9E0F00EA4DBD;\n\t\t\tremoteInfo = CodeEdit;\n\t\t};\n\t\tB658FB4827DA9E1000EA4DBD /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = B658FB2427DA9E0F00EA4DBD /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = B658FB2B27DA9E0F00EA4DBD;\n\t\t\tremoteInfo = CodeEdit;\n\t\t};\n/* End PBXContainerItemProxy section */\n\n/* Begin PBXCopyFilesBuildPhase section */\n\t\t04C3255A2801B43A00C8DA2D /* Embed Frameworks */ = {\n\t\t\tisa = PBXCopyFilesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tdstPath = \"\";\n\t\t\tdstSubfolderSpec = 10;\n\t\t\tfiles = (\n\t\t\t\t6C6BD6F929CD14D100235D17 /* CodeEditKit in Embed Frameworks */,\n\t\t\t);\n\t\t\tname = \"Embed Frameworks\";\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t2BE487F528245162003F3F64 /* Embed Foundation Extensions */ = {\n\t\t\tisa = PBXCopyFilesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tdstPath = \"\";\n\t\t\tdstSubfolderSpec = 13;\n\t\t\tfiles = (\n\t\t\t\t2BE487F428245162003F3F64 /* OpenWithCodeEdit.appex in Embed Foundation Extensions */,\n\t\t\t);\n\t\t\tname = \"Embed Foundation Extensions\";\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6C6BD6FD29CD154900235D17 /* Embed ExtensionKit ExtensionPoint */ = {\n\t\t\tisa = PBXCopyFilesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tdstPath = \"$(EXTENSIONS_FOLDER_PATH)\";\n\t\t\tdstSubfolderSpec = 16;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tname = \"Embed ExtensionKit ExtensionPoint\";\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXCopyFilesBuildPhase section */\n\n/* Begin PBXFileReference section */\n\t\t283BDCBC2972EEBD002AFF81 /* Package.resolved */ = {isa = PBXFileReference; lastKnownFileType = text; name = Package.resolved; path = CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved; sourceTree = \"<group>\"; };\n\t\t284DC8502978BA2600BF2770 /* .all-contributorsrc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = \".all-contributorsrc\"; sourceTree = \"<group>\"; };\n\t\t2BE487EC28245162003F3F64 /* OpenWithCodeEdit.appex */ = {isa = PBXFileReference; explicitFileType = \"wrapper.app-extension\"; includeInIndex = 0; path = OpenWithCodeEdit.appex; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t589F3E342936185400E1A4DA /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/MacOSX.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };\n\t\t58F2EACE292FB2B0004A9BDE /* Documentation.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = Documentation.docc; sourceTree = \"<group>\"; };\n\t\t6C67413D2C44A28C00AABDF5 /* ProjectNavigatorViewController+DataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = \"ProjectNavigatorViewController+DataSource.swift\"; sourceTree = \"<group>\"; };\n\t\t6C67413F2C44A2A200AABDF5 /* ProjectNavigatorViewController+Delegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = \"ProjectNavigatorViewController+Delegate.swift\"; sourceTree = \"<group>\"; };\n\t\t6C9619262C3F285C009733CE /* CodeEditTestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = CodeEditTestPlan.xctestplan; sourceTree = \"<group>\"; };\n\t\tB658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CodeEdit.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tB658FB3D27DA9E1000EA4DBD /* CodeEditTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CodeEditTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tB658FB4727DA9E1000EA4DBD /* CodeEditUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CodeEditUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tB6FF04772B6C08AC002C2C78 /* DefaultThemes */ = {isa = PBXFileReference; lastKnownFileType = folder; path = DefaultThemes; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */\n\t\tB62454482D78A3CC009A86D1 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {\n\t\t\tisa = PBXFileSystemSynchronizedBuildFileExceptionSet;\n\t\t\tmembershipExceptions = (\n\t\t\t\tInfo.plist,\n\t\t\t);\n\t\t\ttarget = B658FB2B27DA9E0F00EA4DBD /* CodeEdit */;\n\t\t};\n\t\tB62454D02D78A3D8009A86D1 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {\n\t\t\tisa = PBXFileSystemSynchronizedBuildFileExceptionSet;\n\t\t\tmembershipExceptions = (\n\t\t\t\tFinderSync.swift,\n\t\t\t\tMedia.xcassets,\n\t\t\t);\n\t\t\ttarget = 2BE487EB28245162003F3F64 /* OpenWithCodeEdit */;\n\t\t};\n/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */\n\n/* Begin PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */\n\t\tB62454492D78A3CC009A86D1 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */ = {\n\t\t\tisa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet;\n\t\t\tbuildPhase = 6C6BD6FD29CD154900235D17 /* Embed ExtensionKit ExtensionPoint */;\n\t\t\tmembershipExceptions = (\n\t\t\t\tFeatures/Extensions/codeedit.extension.appextensionpoint,\n\t\t\t);\n\t\t};\n/* End PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */\n\n/* Begin PBXFileSystemSynchronizedRootGroup section */\n\t\tB624520B2D78A3CC009A86D1 /* CodeEdit */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (B62454482D78A3CC009A86D1 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, B62454492D78A3CC009A86D1 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = CodeEdit; sourceTree = \"<group>\"; };\n\t\tB624544F2D78A3D3009A86D1 /* Configs */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Configs; sourceTree = \"<group>\"; };\n\t\tB62454602D78A3D4009A86D1 /* CodeEditUITests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = CodeEditUITests; sourceTree = \"<group>\"; };\n\t\tB62454A32D78A3D4009A86D1 /* CodeEditTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = CodeEditTests; sourceTree = \"<group>\"; };\n\t\tB62454CD2D78A3D8009A86D1 /* OpenWithCodeEdit */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (B62454D02D78A3D8009A86D1 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = OpenWithCodeEdit; sourceTree = \"<group>\"; };\n/* End PBXFileSystemSynchronizedRootGroup section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t2BE487E928245162003F3F64 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tB658FB2927DA9E0F00EA4DBD /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t302AD7FF2D8054D500231E16 /* ZIPFoundation in Frameworks */,\n\t\t\t\t6C85BB402C2105ED00EB5DEF /* CodeEditKit in Frameworks */,\n\t\t\t\t6C66C31329D05CDC00DE9ED2 /* GRDB in Frameworks */,\n\t\t\t\t58F2EB1E292FB954004A9BDE /* Sparkle in Frameworks */,\n\t\t\t\t6C147C4529A329350089B630 /* OrderedCollections in Frameworks */,\n\t\t\t\t6CE21E872C650D2C0031B056 /* SwiftTerm in Frameworks */,\n\t\t\t\t6C76D6D42E15B91E00EF52C3 /* CodeEditSourceEditor in Frameworks */,\n\t\t\t\t6CCF73D02E26DE3200B94F75 /* SwiftTerm in Frameworks */,\n\t\t\t\t6C315FC82E05E33D0011BFC5 /* CodeEditSourceEditor in Frameworks */,\n\t\t\t\t6CC00A8B2CBEF150004E8134 /* CodeEditSourceEditor in Frameworks */,\n\t\t\t\t6CD3CA552C8B508200D83DCD /* CodeEditSourceEditor in Frameworks */,\n\t\t\t\t6C0617D62BDB4432008C9C42 /* LogStream in Frameworks */,\n\t\t\t\t6CC17B4F2C432AE000834E2C /* CodeEditSourceEditor in Frameworks */,\n\t\t\t\t6CCF6DD32E26D48F00B94F75 /* SwiftTerm in Frameworks */,\n\t\t\t\t30CB64912C16CA8100CC8A9E /* LanguageServerProtocol in Frameworks */,\n\t\t\t\t5E4485612DF600D9008BBE69 /* AboutWindow in Frameworks */,\n\t\t\t\t6C6BD6F429CD142C00235D17 /* CollectionConcurrencyKit in Frameworks */,\n\t\t\t\t6C85BB442C210EFD00EB5DEF /* SwiftUIIntrospect in Frameworks */,\n\t\t\t\t6CB446402B6DFF3A00539ED0 /* CodeEditSourceEditor in Frameworks */,\n\t\t\t\t6C73A6D32D4F1E550012D95C /* CodeEditSourceEditor in Frameworks */,\n\t\t\t\t2816F594280CF50500DD548B /* CodeEditSymbols in Frameworks */,\n\t\t\t\t30CB64942C16CA9100CC8A9E /* LanguageClient in Frameworks */,\n\t\t\t\t5EACE6222DF4BF08005E08B8 /* WelcomeWindow in Frameworks */,\n\t\t\t\t6C6BD6F829CD14D100235D17 /* CodeEditKit in Frameworks */,\n\t\t\t\t6C0824A12C5C0C9700A0751E /* SwiftTerm in Frameworks */,\n\t\t\t\t6C81916B29B41DD300B75C92 /* DequeModule in Frameworks */,\n\t\t\t\t6CB94D032CA1205100E8651C /* AsyncAlgorithms in Frameworks */,\n\t\t\t\t6C9DB9E42D55656300ACD86E /* CodeEditSourceEditor in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tB658FB3A27DA9E1000EA4DBD /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t583E529C29361BAB001AB554 /* SnapshotTesting in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tB658FB4427DA9E1000EA4DBD /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t5C403B8D27E20F8000788241 /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t589F3E342936185400E1A4DA /* XCTest.framework */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6C01F25D2C4820B600AA951B /* Recovered References */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6C67413D2C44A28C00AABDF5 /* ProjectNavigatorViewController+DataSource.swift */,\n\t\t\t\t6C67413F2C44A2A200AABDF5 /* ProjectNavigatorViewController+Delegate.swift */,\n\t\t\t);\n\t\t\tname = \"Recovered References\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB658FB2327DA9E0F00EA4DBD = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB624520B2D78A3CC009A86D1 /* CodeEdit */,\n\t\t\t\tB62454A32D78A3D4009A86D1 /* CodeEditTests */,\n\t\t\t\tB62454602D78A3D4009A86D1 /* CodeEditUITests */,\n\t\t\t\tB624544F2D78A3D3009A86D1 /* Configs */,\n\t\t\t\tB6FF04772B6C08AC002C2C78 /* DefaultThemes */,\n\t\t\t\t58F2EACE292FB2B0004A9BDE /* Documentation.docc */,\n\t\t\t\tB62454CD2D78A3D8009A86D1 /* OpenWithCodeEdit */,\n\t\t\t\t6C9619262C3F285C009733CE /* CodeEditTestPlan.xctestplan */,\n\t\t\t\t284DC8502978BA2600BF2770 /* .all-contributorsrc */,\n\t\t\t\t283BDCBC2972EEBD002AFF81 /* Package.resolved */,\n\t\t\t\tB658FB2D27DA9E0F00EA4DBD /* Products */,\n\t\t\t\t5C403B8D27E20F8000788241 /* Frameworks */,\n\t\t\t\t6C01F25D2C4820B600AA951B /* Recovered References */,\n\t\t\t);\n\t\t\tindentWidth = 4;\n\t\t\tsourceTree = \"<group>\";\n\t\t\ttabWidth = 4;\n\t\t};\n\t\tB658FB2D27DA9E0F00EA4DBD /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */,\n\t\t\t\tB658FB3D27DA9E1000EA4DBD /* CodeEditTests.xctest */,\n\t\t\t\tB658FB4727DA9E1000EA4DBD /* CodeEditUITests.xctest */,\n\t\t\t\t2BE487EC28245162003F3F64 /* OpenWithCodeEdit.appex */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t2BE487EB28245162003F3F64 /* OpenWithCodeEdit */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 2BE487F828245162003F3F64 /* Build configuration list for PBXNativeTarget \"OpenWithCodeEdit\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t2BE487E828245162003F3F64 /* Sources */,\n\t\t\t\t2BE487E928245162003F3F64 /* Frameworks */,\n\t\t\t\t2BE487EA28245162003F3F64 /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = OpenWithCodeEdit;\n\t\t\tproductName = OpenWithCodeEdit;\n\t\t\tproductReference = 2BE487EC28245162003F3F64 /* OpenWithCodeEdit.appex */;\n\t\t\tproductType = \"com.apple.product-type.app-extension\";\n\t\t};\n\t\tB658FB2B27DA9E0F00EA4DBD /* CodeEdit */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = B658FB5127DA9E1000EA4DBD /* Build configuration list for PBXNativeTarget \"CodeEdit\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tB658FB2827DA9E0F00EA4DBD /* Sources */,\n\t\t\t\tB658FB2927DA9E0F00EA4DBD /* Frameworks */,\n\t\t\t\tB658FB2A27DA9E0F00EA4DBD /* Resources */,\n\t\t\t\t2B18499A27F8A7A0005119F0 /* Mark // swiftlint:disable:all as errors | Run Script */,\n\t\t\t\t04ADA0CC27E6043B00BF00B2 /* Add TODO/FIXME as warnings | Run Script */,\n\t\t\t\t04C3255A2801B43A00C8DA2D /* Embed Frameworks */,\n\t\t\t\t2BE487F528245162003F3F64 /* Embed Foundation Extensions */,\n\t\t\t\t6C6BD6FD29CD154900235D17 /* Embed ExtensionKit ExtensionPoint */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\t6C7B1C762A1D57CE005CBBFC /* PBXTargetDependency */,\n\t\t\t\t2BE487F328245162003F3F64 /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tfileSystemSynchronizedGroups = (\n\t\t\t\tB624520B2D78A3CC009A86D1 /* CodeEdit */,\n\t\t\t);\n\t\t\tname = CodeEdit;\n\t\t\tpackageProductDependencies = (\n\t\t\t\t2816F593280CF50500DD548B /* CodeEditSymbols */,\n\t\t\t\t58F2EB1D292FB954004A9BDE /* Sparkle */,\n\t\t\t\t6C147C4429A329350089B630 /* OrderedCollections */,\n\t\t\t\t6C81916A29B41DD300B75C92 /* DequeModule */,\n\t\t\t\t6C6BD6F329CD142C00235D17 /* CollectionConcurrencyKit */,\n\t\t\t\t6C6BD6F729CD14D100235D17 /* CodeEditKit */,\n\t\t\t\t6C66C31229D05CDC00DE9ED2 /* GRDB */,\n\t\t\t\t6CB4463F2B6DFF3A00539ED0 /* CodeEditSourceEditor */,\n\t\t\t\t6C0617D52BDB4432008C9C42 /* LogStream */,\n\t\t\t\t6C85BB3F2C2105ED00EB5DEF /* CodeEditKit */,\n\t\t\t\t6C85BB432C210EFD00EB5DEF /* SwiftUIIntrospect */,\n\t\t\t\t6CC17B4E2C432AE000834E2C /* CodeEditSourceEditor */,\n\t\t\t\t6C0824A02C5C0C9700A0751E /* SwiftTerm */,\n\t\t\t\t6CE21E862C650D2C0031B056 /* SwiftTerm */,\n\t\t\t\t6CD3CA542C8B508200D83DCD /* CodeEditSourceEditor */,\n\t\t\t\t6CB94D022CA1205100E8651C /* AsyncAlgorithms */,\n\t\t\t\t6CC00A8A2CBEF150004E8134 /* CodeEditSourceEditor */,\n\t\t\t\t30818CB42D4E563900967860 /* ZIPFoundation */,\n\t\t\t\t6C73A6D22D4F1E550012D95C /* CodeEditSourceEditor */,\n\t\t\t\t5EACE6212DF4BF08005E08B8 /* WelcomeWindow */,\n\t\t\t\t5E4485602DF600D9008BBE69 /* AboutWindow */,\n\t\t\t\t6C315FC72E05E33D0011BFC5 /* CodeEditSourceEditor */,\n\t\t\t\t6C76D6D32E15B91E00EF52C3 /* CodeEditSourceEditor */,\n\t\t\t\t6CCF6DD22E26D48F00B94F75 /* SwiftTerm */,\n\t\t\t\t6CCF73CF2E26DE3200B94F75 /* SwiftTerm */,\n\t\t\t);\n\t\t\tproductName = CodeEdit;\n\t\t\tproductReference = B658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n\t\tB658FB3C27DA9E1000EA4DBD /* CodeEditTests */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = B658FB5427DA9E1000EA4DBD /* Build configuration list for PBXNativeTarget \"CodeEditTests\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tB658FB3927DA9E1000EA4DBD /* Sources */,\n\t\t\t\tB658FB3A27DA9E1000EA4DBD /* Frameworks */,\n\t\t\t\tB658FB3B27DA9E1000EA4DBD /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\tB658FB3F27DA9E1000EA4DBD /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tfileSystemSynchronizedGroups = (\n\t\t\t\tB62454A32D78A3D4009A86D1 /* CodeEditTests */,\n\t\t\t);\n\t\t\tname = CodeEditTests;\n\t\t\tpackageProductDependencies = (\n\t\t\t\t583E529B29361BAB001AB554 /* SnapshotTesting */,\n\t\t\t);\n\t\t\tproductName = CodeEditTests;\n\t\t\tproductReference = B658FB3D27DA9E1000EA4DBD /* CodeEditTests.xctest */;\n\t\t\tproductType = \"com.apple.product-type.bundle.unit-test\";\n\t\t};\n\t\tB658FB4627DA9E1000EA4DBD /* CodeEditUITests */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = B658FB5727DA9E1000EA4DBD /* Build configuration list for PBXNativeTarget \"CodeEditUITests\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tB658FB4327DA9E1000EA4DBD /* Sources */,\n\t\t\t\tB658FB4427DA9E1000EA4DBD /* Frameworks */,\n\t\t\t\tB658FB4527DA9E1000EA4DBD /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\tB658FB4927DA9E1000EA4DBD /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tfileSystemSynchronizedGroups = (\n\t\t\t\tB62454602D78A3D4009A86D1 /* CodeEditUITests */,\n\t\t\t);\n\t\t\tname = CodeEditUITests;\n\t\t\tpackageProductDependencies = (\n\t\t\t);\n\t\t\tproductName = CodeEditUITests;\n\t\t\tproductReference = B658FB4727DA9E1000EA4DBD /* CodeEditUITests.xctest */;\n\t\t\tproductType = \"com.apple.product-type.bundle.ui-testing\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\tB658FB2427DA9E0F00EA4DBD /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tBuildIndependentTargetsInParallel = 1;\n\t\t\t\tLastSwiftUpdateCheck = 1330;\n\t\t\t\tLastUpgradeCheck = 2610;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t2BE487EB28245162003F3F64 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 13.3.1;\n\t\t\t\t\t};\n\t\t\t\t\tB658FB2B27DA9E0F00EA4DBD = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 13.1;\n\t\t\t\t\t};\n\t\t\t\t\tB658FB3C27DA9E1000EA4DBD = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 13.1;\n\t\t\t\t\t\tTestTargetID = B658FB2B27DA9E0F00EA4DBD;\n\t\t\t\t\t};\n\t\t\t\t\tB658FB4627DA9E1000EA4DBD = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 13.1;\n\t\t\t\t\t\tLastSwiftMigration = 1410;\n\t\t\t\t\t\tTestTargetID = B658FB2B27DA9E0F00EA4DBD;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = B658FB2727DA9E0F00EA4DBD /* Build configuration list for PBXProject \"CodeEdit\" */;\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = B658FB2327DA9E0F00EA4DBD;\n\t\t\tpackageReferences = (\n\t\t\t\t2816F592280CF50500DD548B /* XCRemoteSwiftPackageReference \"CodeEditSymbols\" */,\n\t\t\t\t287136B1292A407E00E9F5F4 /* XCRemoteSwiftPackageReference \"SwiftLintPlugin\" */,\n\t\t\t\t58F2EB1C292FB954004A9BDE /* XCRemoteSwiftPackageReference \"Sparkle\" */,\n\t\t\t\t583E529A29361BAB001AB554 /* XCRemoteSwiftPackageReference \"swift-snapshot-testing\" */,\n\t\t\t\t6C147C4329A329350089B630 /* XCRemoteSwiftPackageReference \"swift-collections\" */,\n\t\t\t\t6C6BD6F229CD142C00235D17 /* XCRemoteSwiftPackageReference \"collectionconcurrencykit\" */,\n\t\t\t\t6C66C31129D05CC800DE9ED2 /* XCRemoteSwiftPackageReference \"GRDB.swift\" */,\n\t\t\t\t6C0617D42BDB4432008C9C42 /* XCRemoteSwiftPackageReference \"LogStream\" */,\n\t\t\t\t6C85BB3E2C2105ED00EB5DEF /* XCRemoteSwiftPackageReference \"CodeEditKit\" */,\n\t\t\t\t6C85BB422C210EFD00EB5DEF /* XCRemoteSwiftPackageReference \"SwiftUI-Introspect\" */,\n\t\t\t\t303E88452C276FD100EEA8D9 /* XCRemoteSwiftPackageReference \"LanguageClient\" */,\n\t\t\t\t303E88462C276FD600EEA8D9 /* XCRemoteSwiftPackageReference \"LanguageServerProtocol\" */,\n\t\t\t\t6CB94D012CA1205100E8651C /* XCRemoteSwiftPackageReference \"swift-async-algorithms\" */,\n\t\t\t\t30ED7B722DD299E600ACC922 /* XCRemoteSwiftPackageReference \"ZIPFoundation\" */,\n\t\t\t\t5EACE6202DF4BF08005E08B8 /* XCRemoteSwiftPackageReference \"WelcomeWindow\" */,\n\t\t\t\t5E44855F2DF600D9008BBE69 /* XCRemoteSwiftPackageReference \"AboutWindow\" */,\n\t\t\t\t6C76D6D22E15B91E00EF52C3 /* XCRemoteSwiftPackageReference \"CodeEditSourceEditor\" */,\n\t\t\t\t6CCF73CE2E26DE3200B94F75 /* XCRemoteSwiftPackageReference \"SwiftTerm\" */,\n\t\t\t);\n\t\t\tpreferredProjectObjectVersion = 55;\n\t\t\tproductRefGroup = B658FB2D27DA9E0F00EA4DBD /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\tB658FB2B27DA9E0F00EA4DBD /* CodeEdit */,\n\t\t\t\tB658FB3C27DA9E1000EA4DBD /* CodeEditTests */,\n\t\t\t\tB658FB4627DA9E1000EA4DBD /* CodeEditUITests */,\n\t\t\t\t2BE487EB28245162003F3F64 /* OpenWithCodeEdit */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t2BE487EA28245162003F3F64 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tB658FB2A27DA9E0F00EA4DBD /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tB6FF04782B6C08AC002C2C78 /* DefaultThemes in Resources */,\n\t\t\t\t283BDCBD2972EEBD002AFF81 /* Package.resolved in Resources */,\n\t\t\t\t284DC8512978BA2600BF2770 /* .all-contributorsrc in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tB658FB3B27DA9E1000EA4DBD /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tB658FB4527DA9E1000EA4DBD /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\t04ADA0CC27E6043B00BF00B2 /* Add TODO/FIXME as warnings | Run Script */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\talwaysOutOfDate = 1;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Add TODO/FIXME as warnings | Run Script\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"TAGS=\\\"TODO:|FIXME:\\\"\\necho \\\"searching ${SRCROOT} for ${TAGS}\\\"\\nfind \\\"${SRCROOT}\\\" \\\\( -name \\\"*.swift\\\" \\\\) -print0 | xargs -0 egrep --with-filename --line-number --only-matching \\\"($TAGS).*\\\\$\\\" | perl -p -e \\\"s/($TAGS)/ warning: \\\\$1/\\\"\\n\";\n\t\t};\n\t\t2B18499A27F8A7A0005119F0 /* Mark // swiftlint:disable:all as errors | Run Script */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\talwaysOutOfDate = 1;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Mark // swiftlint:disable:all as errors | Run Script\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"TAGS=\\\"\\\\/\\\\/ swiftlint:disable all\\\"\\necho \\\"searching ${SRCROOT} for ${TAGS}\\\"\\nfind \\\"${SRCROOT}\\\" \\\\( -name \\\"*.swift\\\" \\\\) -print0 | xargs -0 egrep --with-filename --line-number --only-matching \\\"($TAGS).*\\\\$\\\" | perl -p -e \\\"s/($TAGS)/ error: Usage of \\\\$1 is prohibited/\\\"\\n\";\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t2BE487E828245162003F3F64 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tB658FB2827DA9E0F00EA4DBD /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6CAAF69429BCD78600A1F48A /* (null) in Sources */,\n\t\t\t\t58F2EB03292FB2B0004A9BDE /* Documentation.docc in Sources */,\n\t\t\t\t6CB9144B29BEC7F100BC47F2 /* (null) in Sources */,\n\t\t\t\t6CAAF69229BCC71C00A1F48A /* (null) in Sources */,\n\t\t\t\t6CAAF68A29BC9C2300A1F48A /* (null) in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tB658FB3927DA9E1000EA4DBD /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tB658FB4327DA9E1000EA4DBD /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin PBXTargetDependency section */\n\t\t2BE487F328245162003F3F64 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 2BE487EB28245162003F3F64 /* OpenWithCodeEdit */;\n\t\t\ttargetProxy = 2BE487F228245162003F3F64 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6C7B1C762A1D57CE005CBBFC /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\tproductRef = 6C7B1C752A1D57CE005CBBFC /* SwiftLint */;\n\t\t};\n\t\tB658FB3F27DA9E1000EA4DBD /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = B658FB2B27DA9E0F00EA4DBD /* CodeEdit */;\n\t\t\ttargetProxy = B658FB3E27DA9E1000EA4DBD /* PBXContainerItemProxy */;\n\t\t};\n\t\tB658FB4927DA9E1000EA4DBD /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = B658FB2B27DA9E0F00EA4DBD /* CodeEdit */;\n\t\t\ttargetProxy = B658FB4827DA9E1000EA4DBD /* PBXContainerItemProxy */;\n\t\t};\n/* End PBXTargetDependency section */\n\n/* Begin XCBuildConfiguration section */\n\t\t28052DEA2973045200F4F90A /* Alpha */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Alpha.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++17\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMARKETING_VERSION = \"\";\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tOTHER_SWIFT_FLAGS = \"-D ALPHA\";\n\t\t\t\tRUN_DOCUMENTATION_COMPILER = YES;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tSTRING_CATALOG_GENERATE_SYMBOLS = YES;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-O\";\n\t\t\t\tSYSTEM_FRAMEWORK_SEARCH_PATHS = \"\";\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Alpha;\n\t\t};\n\t\t28052DEB2973045200F4F90A /* Alpha */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Alpha.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = \"${CE_APPICON_NAME}\";\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = CodeEdit/CodeEdit.entitlements;\n\t\t\t\tCODE_SIGN_IDENTITY = \"Apple Development\";\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\\\"CodeEdit/Preview Content\\\"\";\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tENABLE_APP_SANDBOX = YES;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tGENERATE_INFOPLIST_FILE = NO;\n\t\t\t\tINFOPLIST_FILE = CodeEdit/Info.plist;\n\t\t\t\tINFOPLIST_KEY_LSApplicationCategoryType = \"public.app-category.developer-tools\";\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"Copyright © 2022-2025 CodeEdit\";\n\t\t\t\tINFOPLIST_KEY_NSPrincipalClass = CodeEdit.CodeEditApplication;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMARKETING_VERSION = \"Change in Info.plist\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = app.codeedit.CodeEdit;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tRUNTIME_EXCEPTION_ALLOW_JIT = YES;\n\t\t\t\tRUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = YES;\n\t\t\t\tRUN_DOCUMENTATION_COMPILER = NO;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tSYSTEM_FRAMEWORK_SEARCH_PATHS = \"$(inherited)\";\n\t\t\t};\n\t\t\tname = Alpha;\n\t\t};\n\t\t28052DEC2973045200F4F90A /* Alpha */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Alpha.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tBUNDLE_LOADER = \"$(TEST_HOST)\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t\t\"@loader_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = app.codeedit.CodeEditTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = NO;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTEST_HOST = \"$(BUILT_PRODUCTS_DIR)/CodeEdit.app/Contents/MacOS/CodeEdit\";\n\t\t\t};\n\t\t\tname = Alpha;\n\t\t};\n\t\t28052DED2973045200F4F90A /* Alpha */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Alpha.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t\t\"@loader_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = app.codeedit.CodeEditUITests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE = \"\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = NO;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTEST_TARGET_NAME = CodeEdit;\n\t\t\t};\n\t\t\tname = Alpha;\n\t\t};\n\t\t28052DEE2973045200F4F90A /* Alpha */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Alpha.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = OpenWithCodeEdit/OpenWithCodeEdit.entitlements;\n\t\t\t\tCODE_SIGN_IDENTITY = \"-\";\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tGENERATE_INFOPLIST_FILE = NO;\n\t\t\t\tINFOPLIST_FILE = OpenWithCodeEdit/Info.plist;\n\t\t\t\tINFOPLIST_KEY_CFBundleDisplayName = OpenWithCodeEdit;\n\t\t\t\tINFOPLIST_KEY_LSUIElement = YES;\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t\t\"@executable_path/../../../../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = app.codeedit.CodeEdit.OpenWithCodeEdit;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Alpha;\n\t\t};\n\t\t28052DEF2973045C00F4F90A /* Beta */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Beta.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++17\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMARKETING_VERSION = \"\";\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tOTHER_SWIFT_FLAGS = \"-D BETA\";\n\t\t\t\tRUN_DOCUMENTATION_COMPILER = YES;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tSTRING_CATALOG_GENERATE_SYMBOLS = YES;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-O\";\n\t\t\t\tSYSTEM_FRAMEWORK_SEARCH_PATHS = \"\";\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Beta;\n\t\t};\n\t\t28052DF02973045C00F4F90A /* Beta */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Beta.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = \"${CE_APPICON_NAME}\";\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = CodeEdit/CodeEdit.entitlements;\n\t\t\t\tCODE_SIGN_IDENTITY = \"Apple Development\";\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\\\"CodeEdit/Preview Content\\\"\";\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tENABLE_APP_SANDBOX = YES;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tGENERATE_INFOPLIST_FILE = NO;\n\t\t\t\tINFOPLIST_FILE = CodeEdit/Info.plist;\n\t\t\t\tINFOPLIST_KEY_LSApplicationCategoryType = \"public.app-category.developer-tools\";\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"Copyright © 2022-2025 CodeEdit\";\n\t\t\t\tINFOPLIST_KEY_NSPrincipalClass = CodeEdit.CodeEditApplication;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMARKETING_VERSION = \"Change in Info.plist\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = app.codeedit.CodeEdit;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tRUNTIME_EXCEPTION_ALLOW_JIT = YES;\n\t\t\t\tRUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = YES;\n\t\t\t\tRUN_DOCUMENTATION_COMPILER = NO;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tSYSTEM_FRAMEWORK_SEARCH_PATHS = \"$(inherited)\";\n\t\t\t};\n\t\t\tname = Beta;\n\t\t};\n\t\t28052DF12973045C00F4F90A /* Beta */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Beta.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tBUNDLE_LOADER = \"$(TEST_HOST)\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t\t\"@loader_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = app.codeedit.CodeEditTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = NO;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTEST_HOST = \"$(BUILT_PRODUCTS_DIR)/CodeEdit.app/Contents/MacOS/CodeEdit\";\n\t\t\t};\n\t\t\tname = Beta;\n\t\t};\n\t\t28052DF22973045C00F4F90A /* Beta */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Beta.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t\t\"@loader_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = app.codeedit.CodeEditUITests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE = \"\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = NO;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTEST_TARGET_NAME = CodeEdit;\n\t\t\t};\n\t\t\tname = Beta;\n\t\t};\n\t\t28052DF32973045C00F4F90A /* Beta */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Beta.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = OpenWithCodeEdit/OpenWithCodeEdit.entitlements;\n\t\t\t\tCODE_SIGN_IDENTITY = \"-\";\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tGENERATE_INFOPLIST_FILE = NO;\n\t\t\t\tINFOPLIST_FILE = OpenWithCodeEdit/Info.plist;\n\t\t\t\tINFOPLIST_KEY_CFBundleDisplayName = OpenWithCodeEdit;\n\t\t\t\tINFOPLIST_KEY_LSUIElement = YES;\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t\t\"@executable_path/../../../../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = app.codeedit.CodeEdit.OpenWithCodeEdit;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Beta;\n\t\t};\n\t\t2BE487F628245162003F3F64 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Debug.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = OpenWithCodeEdit/OpenWithCodeEdit.entitlements;\n\t\t\t\tCODE_SIGN_IDENTITY = \"-\";\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tGENERATE_INFOPLIST_FILE = NO;\n\t\t\t\tINFOPLIST_FILE = OpenWithCodeEdit/Info.plist;\n\t\t\t\tINFOPLIST_KEY_CFBundleDisplayName = OpenWithCodeEdit;\n\t\t\t\tINFOPLIST_KEY_LSUIElement = YES;\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t\t\"@executable_path/../../../../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = app.codeedit.CodeEdit.OpenWithCodeEdit;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t2BE487F728245162003F3F64 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Release.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = OpenWithCodeEdit/OpenWithCodeEdit.entitlements;\n\t\t\t\tCODE_SIGN_IDENTITY = \"-\";\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tGENERATE_INFOPLIST_FILE = NO;\n\t\t\t\tINFOPLIST_FILE = OpenWithCodeEdit/Info.plist;\n\t\t\t\tINFOPLIST_KEY_CFBundleDisplayName = OpenWithCodeEdit;\n\t\t\t\tINFOPLIST_KEY_LSUIElement = YES;\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t\t\"@executable_path/../../../../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = app.codeedit.CodeEdit.OpenWithCodeEdit;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t8B9A0E112B9FE7D7007E2DBF /* Pre */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Alpha.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCE_APPICON_NAME = AppIconPre;\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++17\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMARKETING_VERSION = \"\";\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tOTHER_SWIFT_FLAGS = \"-D ALPHA\";\n\t\t\t\tRUN_DOCUMENTATION_COMPILER = YES;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tSTRING_CATALOG_GENERATE_SYMBOLS = YES;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-O\";\n\t\t\t\tSYSTEM_FRAMEWORK_SEARCH_PATHS = \"\";\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Pre;\n\t\t};\n\t\t8B9A0E122B9FE7D7007E2DBF /* Pre */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Alpha.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = \"${CE_APPICON_NAME}\";\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCE_APPICON_NAME = AppIconPre;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = CodeEdit/CodeEdit.entitlements;\n\t\t\t\tCODE_SIGN_IDENTITY = \"Apple Development\";\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\\\"CodeEdit/Preview Content\\\"\";\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tENABLE_APP_SANDBOX = YES;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tGENERATE_INFOPLIST_FILE = NO;\n\t\t\t\tINFOPLIST_FILE = CodeEdit/Info.plist;\n\t\t\t\tINFOPLIST_KEY_LSApplicationCategoryType = \"public.app-category.developer-tools\";\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"Copyright © 2022-2025 CodeEdit\";\n\t\t\t\tINFOPLIST_KEY_NSPrincipalClass = CodeEdit.CodeEditApplication;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMARKETING_VERSION = \"Change in Info.plist\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = app.codeedit.CodeEdit;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tRUNTIME_EXCEPTION_ALLOW_JIT = YES;\n\t\t\t\tRUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = YES;\n\t\t\t\tRUN_DOCUMENTATION_COMPILER = NO;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tSYSTEM_FRAMEWORK_SEARCH_PATHS = \"$(inherited)\";\n\t\t\t};\n\t\t\tname = Pre;\n\t\t};\n\t\t8B9A0E132B9FE7D7007E2DBF /* Pre */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Alpha.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tBUNDLE_LOADER = \"$(TEST_HOST)\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t\t\"@loader_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = app.codeedit.CodeEditTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = NO;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTEST_HOST = \"$(BUILT_PRODUCTS_DIR)/CodeEdit.app/Contents/MacOS/CodeEdit\";\n\t\t\t};\n\t\t\tname = Pre;\n\t\t};\n\t\t8B9A0E142B9FE7D7007E2DBF /* Pre */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Alpha.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t\t\"@loader_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = app.codeedit.CodeEditUITests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE = \"\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = NO;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTEST_TARGET_NAME = CodeEdit;\n\t\t\t};\n\t\t\tname = Pre;\n\t\t};\n\t\t8B9A0E152B9FE7D7007E2DBF /* Pre */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Alpha.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = OpenWithCodeEdit/OpenWithCodeEdit.entitlements;\n\t\t\t\tCODE_SIGN_IDENTITY = \"-\";\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tGENERATE_INFOPLIST_FILE = NO;\n\t\t\t\tINFOPLIST_FILE = OpenWithCodeEdit/Info.plist;\n\t\t\t\tINFOPLIST_KEY_CFBundleDisplayName = OpenWithCodeEdit;\n\t\t\t\tINFOPLIST_KEY_LSUIElement = YES;\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t\t\"@executable_path/../../../../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = app.codeedit.CodeEdit.OpenWithCodeEdit;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Pre;\n\t\t};\n\t\tB658FB4F27DA9E1000EA4DBD /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Debug.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++17\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMARKETING_VERSION = \"\";\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tRUN_DOCUMENTATION_COMPILER = YES;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tSTRING_CATALOG_GENERATE_SYMBOLS = YES;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSYSTEM_FRAMEWORK_SEARCH_PATHS = \"\";\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tB658FB5027DA9E1000EA4DBD /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Release.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++17\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMARKETING_VERSION = \"\";\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tRUN_DOCUMENTATION_COMPILER = YES;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tSTRING_CATALOG_GENERATE_SYMBOLS = YES;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-O\";\n\t\t\t\tSYSTEM_FRAMEWORK_SEARCH_PATHS = \"\";\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tB658FB5227DA9E1000EA4DBD /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Debug.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = \"${CE_APPICON_NAME}\";\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = CodeEdit/CodeEdit.entitlements;\n\t\t\t\tCODE_SIGN_IDENTITY = \"-\";\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"Apple Development\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\\\"CodeEdit/Preview Content\\\"\";\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tENABLE_APP_SANDBOX = YES;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tGENERATE_INFOPLIST_FILE = NO;\n\t\t\t\tINFOPLIST_FILE = CodeEdit/Info.plist;\n\t\t\t\tINFOPLIST_KEY_LSApplicationCategoryType = \"public.app-category.developer-tools\";\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"Copyright © 2022-2025 CodeEdit\";\n\t\t\t\tINFOPLIST_KEY_NSPrincipalClass = CodeEdit.CodeEditApplication;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMARKETING_VERSION = \"Change in Info.plist\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = app.codeedit.CodeEdit;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tRUNTIME_EXCEPTION_ALLOW_JIT = YES;\n\t\t\t\tRUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = YES;\n\t\t\t\tRUN_DOCUMENTATION_COMPILER = NO;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tSYSTEM_FRAMEWORK_SEARCH_PATHS = \"$(inherited)\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tB658FB5327DA9E1000EA4DBD /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Release.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = \"${CE_APPICON_NAME}\";\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = CodeEdit/CodeEdit.entitlements;\n\t\t\t\tCODE_SIGN_IDENTITY = \"Apple Development\";\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\\\"CodeEdit/Preview Content\\\"\";\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tENABLE_APP_SANDBOX = YES;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tGENERATE_INFOPLIST_FILE = NO;\n\t\t\t\tINFOPLIST_FILE = CodeEdit/Info.plist;\n\t\t\t\tINFOPLIST_KEY_LSApplicationCategoryType = \"public.app-category.developer-tools\";\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"Copyright © 2022-2025 CodeEdit\";\n\t\t\t\tINFOPLIST_KEY_NSPrincipalClass = CodeEdit.CodeEditApplication;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMARKETING_VERSION = \"Change in Info.plist\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = app.codeedit.CodeEdit;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tRUNTIME_EXCEPTION_ALLOW_JIT = YES;\n\t\t\t\tRUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = YES;\n\t\t\t\tRUN_DOCUMENTATION_COMPILER = NO;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tSYSTEM_FRAMEWORK_SEARCH_PATHS = \"$(inherited)\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tB658FB5527DA9E1000EA4DBD /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Debug.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tBUNDLE_LOADER = \"$(TEST_HOST)\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t\t\"@loader_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = app.codeedit.CodeEditTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = NO;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTEST_HOST = \"$(BUILT_PRODUCTS_DIR)/CodeEdit.app/Contents/MacOS/CodeEdit\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tB658FB5627DA9E1000EA4DBD /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Release.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tBUNDLE_LOADER = \"$(TEST_HOST)\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t\t\"@loader_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = app.codeedit.CodeEditTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = NO;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTEST_HOST = \"$(BUILT_PRODUCTS_DIR)/CodeEdit.app/Contents/MacOS/CodeEdit\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tB658FB5827DA9E1000EA4DBD /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Debug.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t\t\"@loader_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = app.codeedit.CodeEditUITests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE = \"\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = NO;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTEST_TARGET_NAME = CodeEdit;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tB658FB5927DA9E1000EA4DBD /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReferenceAnchor = B624544F2D78A3D3009A86D1 /* Configs */;\n\t\t\tbaseConfigurationReferenceRelativePath = Release.xcconfig;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 47;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t\t\"@loader_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = app.codeedit.CodeEditUITests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE = \"\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = NO;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTEST_TARGET_NAME = CodeEdit;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t2BE487F828245162003F3F64 /* Build configuration list for PBXNativeTarget \"OpenWithCodeEdit\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t2BE487F628245162003F3F64 /* Debug */,\n\t\t\t\t2BE487F728245162003F3F64 /* Release */,\n\t\t\t\t28052DEE2973045200F4F90A /* Alpha */,\n\t\t\t\t8B9A0E152B9FE7D7007E2DBF /* Pre */,\n\t\t\t\t28052DF32973045C00F4F90A /* Beta */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tB658FB2727DA9E0F00EA4DBD /* Build configuration list for PBXProject \"CodeEdit\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tB658FB4F27DA9E1000EA4DBD /* Debug */,\n\t\t\t\tB658FB5027DA9E1000EA4DBD /* Release */,\n\t\t\t\t28052DEA2973045200F4F90A /* Alpha */,\n\t\t\t\t8B9A0E112B9FE7D7007E2DBF /* Pre */,\n\t\t\t\t28052DEF2973045C00F4F90A /* Beta */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tB658FB5127DA9E1000EA4DBD /* Build configuration list for PBXNativeTarget \"CodeEdit\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tB658FB5227DA9E1000EA4DBD /* Debug */,\n\t\t\t\tB658FB5327DA9E1000EA4DBD /* Release */,\n\t\t\t\t28052DEB2973045200F4F90A /* Alpha */,\n\t\t\t\t8B9A0E122B9FE7D7007E2DBF /* Pre */,\n\t\t\t\t28052DF02973045C00F4F90A /* Beta */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tB658FB5427DA9E1000EA4DBD /* Build configuration list for PBXNativeTarget \"CodeEditTests\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tB658FB5527DA9E1000EA4DBD /* Debug */,\n\t\t\t\tB658FB5627DA9E1000EA4DBD /* Release */,\n\t\t\t\t28052DEC2973045200F4F90A /* Alpha */,\n\t\t\t\t8B9A0E132B9FE7D7007E2DBF /* Pre */,\n\t\t\t\t28052DF12973045C00F4F90A /* Beta */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tB658FB5727DA9E1000EA4DBD /* Build configuration list for PBXNativeTarget \"CodeEditUITests\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tB658FB5827DA9E1000EA4DBD /* Debug */,\n\t\t\t\tB658FB5927DA9E1000EA4DBD /* Release */,\n\t\t\t\t28052DED2973045200F4F90A /* Alpha */,\n\t\t\t\t8B9A0E142B9FE7D7007E2DBF /* Pre */,\n\t\t\t\t28052DF22973045C00F4F90A /* Beta */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\n/* Begin XCRemoteSwiftPackageReference section */\n\t\t2816F592280CF50500DD548B /* XCRemoteSwiftPackageReference \"CodeEditSymbols\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/CodeEditApp/CodeEditSymbols\";\n\t\t\trequirement = {\n\t\t\t\tkind = exactVersion;\n\t\t\t\tversion = 0.2.3;\n\t\t\t};\n\t\t};\n\t\t287136B1292A407E00E9F5F4 /* XCRemoteSwiftPackageReference \"SwiftLintPlugin\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/lukepistrol/SwiftLintPlugin\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 0.58.2;\n\t\t\t};\n\t\t};\n\t\t303E88452C276FD100EEA8D9 /* XCRemoteSwiftPackageReference \"LanguageClient\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/ChimeHQ/LanguageClient\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 0.8.2;\n\t\t\t};\n\t\t};\n\t\t303E88462C276FD600EEA8D9 /* XCRemoteSwiftPackageReference \"LanguageServerProtocol\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/ChimeHQ/LanguageServerProtocol\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 0.13.2;\n\t\t\t};\n\t\t};\n\t\t30818CB32D4E563900967860 /* XCRemoteSwiftPackageReference \"ZIPFoundation\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/weichsel/ZIPFoundation\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 0.9.19;\n\t\t\t};\n\t\t};\n\t\t30CB648F2C16CA8100CC8A9E /* XCRemoteSwiftPackageReference \"LanguageServerProtocol\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/ChimeHQ/LanguageServerProtocol\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 0.13.2;\n\t\t\t};\n\t\t};\n\t\t30CB64922C16CA9100CC8A9E /* XCRemoteSwiftPackageReference \"LanguageClient\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/ChimeHQ/LanguageClient\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 0.8.0;\n\t\t\t};\n\t\t};\n\t\t30ED7B722DD299E600ACC922 /* XCRemoteSwiftPackageReference \"ZIPFoundation\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/weichsel/ZIPFoundation\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 0.9.19;\n\t\t\t};\n\t\t};\n\t\t583E529A29361BAB001AB554 /* XCRemoteSwiftPackageReference \"swift-snapshot-testing\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/pointfreeco/swift-snapshot-testing.git\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMinorVersion;\n\t\t\t\tminimumVersion = 1.14.2;\n\t\t\t};\n\t\t};\n\t\t58F2EB1C292FB954004A9BDE /* XCRemoteSwiftPackageReference \"Sparkle\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/sparkle-project/Sparkle.git\";\n\t\t\trequirement = {\n\t\t\t\tkind = exactVersion;\n\t\t\t\tversion = 2.3.0;\n\t\t\t};\n\t\t};\n\t\t5E44855F2DF600D9008BBE69 /* XCRemoteSwiftPackageReference \"AboutWindow\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/CodeEditApp/AboutWindow\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 1.0.0;\n\t\t\t};\n\t\t};\n\t\t5EACE6202DF4BF08005E08B8 /* XCRemoteSwiftPackageReference \"WelcomeWindow\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/CodeEditApp/WelcomeWindow\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 1.0.0;\n\t\t\t};\n\t\t};\n\t\t6C0617D42BDB4432008C9C42 /* XCRemoteSwiftPackageReference \"LogStream\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/Wouter01/LogStream\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 1.3.0;\n\t\t\t};\n\t\t};\n\t\t6C147C4329A329350089B630 /* XCRemoteSwiftPackageReference \"swift-collections\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/apple/swift-collections.git\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 1.0.0;\n\t\t\t};\n\t\t};\n\t\t6C66C31129D05CC800DE9ED2 /* XCRemoteSwiftPackageReference \"GRDB.swift\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/groue/GRDB.swift.git\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 6.0.0;\n\t\t\t};\n\t\t};\n\t\t6C6BD6F229CD142C00235D17 /* XCRemoteSwiftPackageReference \"collectionconcurrencykit\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/johnsundell/collectionconcurrencykit\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 0.2.0;\n\t\t\t};\n\t\t};\n\t\t6C76D6D22E15B91E00EF52C3 /* XCRemoteSwiftPackageReference \"CodeEditSourceEditor\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/CodeEditApp/CodeEditSourceEditor\";\n\t\t\trequirement = {\n\t\t\t\tkind = exactVersion;\n\t\t\t\tversion = 0.15.1;\n\t\t\t};\n\t\t};\n\t\t6C85BB3E2C2105ED00EB5DEF /* XCRemoteSwiftPackageReference \"CodeEditKit\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/CodeEditApp/CodeEditKit\";\n\t\t\trequirement = {\n\t\t\t\tkind = exactVersion;\n\t\t\t\tversion = 0.1.2;\n\t\t\t};\n\t\t};\n\t\t6C85BB422C210EFD00EB5DEF /* XCRemoteSwiftPackageReference \"SwiftUI-Introspect\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/siteline/SwiftUI-Introspect.git\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 1.2.0;\n\t\t\t};\n\t\t};\n\t\t6C9DB9E22D55656300ACD86E /* XCRemoteSwiftPackageReference \"CodeEditSourceEditor\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/CodeEditApp/CodeEditSourceEditor\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 0.10.0;\n\t\t\t};\n\t\t};\n\t\t6CB94D012CA1205100E8651C /* XCRemoteSwiftPackageReference \"swift-async-algorithms\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/apple/swift-async-algorithms.git\";\n\t\t\trequirement = {\n\t\t\t\tkind = exactVersion;\n\t\t\t\tversion = 1.0.1;\n\t\t\t};\n\t\t};\n\t\t6CCF73CE2E26DE3200B94F75 /* XCRemoteSwiftPackageReference \"SwiftTerm\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/thecoolwinter/SwiftTerm\";\n\t\t\trequirement = {\n\t\t\t\tbranch = codeedit;\n\t\t\t\tkind = branch;\n\t\t\t};\n\t\t};\n/* End XCRemoteSwiftPackageReference section */\n\n/* Begin XCSwiftPackageProductDependency section */\n\t\t2816F593280CF50500DD548B /* CodeEditSymbols */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 2816F592280CF50500DD548B /* XCRemoteSwiftPackageReference \"CodeEditSymbols\" */;\n\t\t\tproductName = CodeEditSymbols;\n\t\t};\n\t\t30818CB42D4E563900967860 /* ZIPFoundation */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 30818CB32D4E563900967860 /* XCRemoteSwiftPackageReference \"ZIPFoundation\" */;\n\t\t\tproductName = ZIPFoundation;\n\t\t};\n\t\t30CB64902C16CA8100CC8A9E /* LanguageServerProtocol */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 30CB648F2C16CA8100CC8A9E /* XCRemoteSwiftPackageReference \"LanguageServerProtocol\" */;\n\t\t\tproductName = LanguageServerProtocol;\n\t\t};\n\t\t30CB64932C16CA9100CC8A9E /* LanguageClient */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 30CB64922C16CA9100CC8A9E /* XCRemoteSwiftPackageReference \"LanguageClient\" */;\n\t\t\tproductName = LanguageClient;\n\t\t};\n\t\t583E529B29361BAB001AB554 /* SnapshotTesting */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 583E529A29361BAB001AB554 /* XCRemoteSwiftPackageReference \"swift-snapshot-testing\" */;\n\t\t\tproductName = SnapshotTesting;\n\t\t};\n\t\t58F2EB1D292FB954004A9BDE /* Sparkle */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 58F2EB1C292FB954004A9BDE /* XCRemoteSwiftPackageReference \"Sparkle\" */;\n\t\t\tproductName = Sparkle;\n\t\t};\n\t\t5E4485602DF600D9008BBE69 /* AboutWindow */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 5E44855F2DF600D9008BBE69 /* XCRemoteSwiftPackageReference \"AboutWindow\" */;\n\t\t\tproductName = AboutWindow;\n\t\t};\n\t\t5EACE6212DF4BF08005E08B8 /* WelcomeWindow */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 5EACE6202DF4BF08005E08B8 /* XCRemoteSwiftPackageReference \"WelcomeWindow\" */;\n\t\t\tproductName = WelcomeWindow;\n\t\t};\n\t\t6C0617D52BDB4432008C9C42 /* LogStream */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 6C0617D42BDB4432008C9C42 /* XCRemoteSwiftPackageReference \"LogStream\" */;\n\t\t\tproductName = LogStream;\n\t\t};\n\t\t6C0824A02C5C0C9700A0751E /* SwiftTerm */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tproductName = SwiftTerm;\n\t\t};\n\t\t6C147C4429A329350089B630 /* OrderedCollections */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 6C147C4329A329350089B630 /* XCRemoteSwiftPackageReference \"swift-collections\" */;\n\t\t\tproductName = OrderedCollections;\n\t\t};\n\t\t6C315FC72E05E33D0011BFC5 /* CodeEditSourceEditor */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tproductName = CodeEditSourceEditor;\n\t\t};\n\t\t6C66C31229D05CDC00DE9ED2 /* GRDB */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 6C66C31129D05CC800DE9ED2 /* XCRemoteSwiftPackageReference \"GRDB.swift\" */;\n\t\t\tproductName = GRDB;\n\t\t};\n\t\t6C6BD6F329CD142C00235D17 /* CollectionConcurrencyKit */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 6C6BD6F229CD142C00235D17 /* XCRemoteSwiftPackageReference \"collectionconcurrencykit\" */;\n\t\t\tproductName = CollectionConcurrencyKit;\n\t\t};\n\t\t6C6BD6F729CD14D100235D17 /* CodeEditKit */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tproductName = CodeEditKit;\n\t\t};\n\t\t6C73A6D22D4F1E550012D95C /* CodeEditSourceEditor */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tproductName = CodeEditSourceEditor;\n\t\t};\n\t\t6C76D6D32E15B91E00EF52C3 /* CodeEditSourceEditor */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 6C76D6D22E15B91E00EF52C3 /* XCRemoteSwiftPackageReference \"CodeEditSourceEditor\" */;\n\t\t\tproductName = CodeEditSourceEditor;\n\t\t};\n\t\t6C7B1C752A1D57CE005CBBFC /* SwiftLint */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 287136B1292A407E00E9F5F4 /* XCRemoteSwiftPackageReference \"SwiftLintPlugin\" */;\n\t\t\tproductName = \"plugin:SwiftLint\";\n\t\t};\n\t\t6C81916A29B41DD300B75C92 /* DequeModule */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 6C147C4329A329350089B630 /* XCRemoteSwiftPackageReference \"swift-collections\" */;\n\t\t\tproductName = DequeModule;\n\t\t};\n\t\t6C85BB3F2C2105ED00EB5DEF /* CodeEditKit */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 6C85BB3E2C2105ED00EB5DEF /* XCRemoteSwiftPackageReference \"CodeEditKit\" */;\n\t\t\tproductName = CodeEditKit;\n\t\t};\n\t\t6C85BB432C210EFD00EB5DEF /* SwiftUIIntrospect */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 6C85BB422C210EFD00EB5DEF /* XCRemoteSwiftPackageReference \"SwiftUI-Introspect\" */;\n\t\t\tproductName = SwiftUIIntrospect;\n\t\t};\n\t\t6C9DB9E32D55656300ACD86E /* CodeEditSourceEditor */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 6C9DB9E22D55656300ACD86E /* XCRemoteSwiftPackageReference \"CodeEditSourceEditor\" */;\n\t\t\tproductName = CodeEditSourceEditor;\n\t\t};\n\t\t6CB4463F2B6DFF3A00539ED0 /* CodeEditSourceEditor */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tproductName = CodeEditSourceEditor;\n\t\t};\n\t\t6CB94D022CA1205100E8651C /* AsyncAlgorithms */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 6CB94D012CA1205100E8651C /* XCRemoteSwiftPackageReference \"swift-async-algorithms\" */;\n\t\t\tproductName = AsyncAlgorithms;\n\t\t};\n\t\t6CC00A8A2CBEF150004E8134 /* CodeEditSourceEditor */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tproductName = CodeEditSourceEditor;\n\t\t};\n\t\t6CC17B4E2C432AE000834E2C /* CodeEditSourceEditor */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tproductName = CodeEditSourceEditor;\n\t\t};\n\t\t6CCF6DD22E26D48F00B94F75 /* SwiftTerm */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tproductName = SwiftTerm;\n\t\t};\n\t\t6CCF73CF2E26DE3200B94F75 /* SwiftTerm */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 6CCF73CE2E26DE3200B94F75 /* XCRemoteSwiftPackageReference \"SwiftTerm\" */;\n\t\t\tproductName = SwiftTerm;\n\t\t};\n\t\t6CD3CA542C8B508200D83DCD /* CodeEditSourceEditor */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tproductName = CodeEditSourceEditor;\n\t\t};\n\t\t6CE21E862C650D2C0031B056 /* SwiftTerm */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tproductName = SwiftTerm;\n\t\t};\n/* End XCSwiftPackageProductDependency section */\n\t};\n\trootObject = B658FB2427DA9E0F00EA4DBD /* Project object */;\n}\n"
  },
  {
    "path": "CodeEdit.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved",
    "content": "{\n  \"originHash\" : \"01191ca9685501db65981a6fd21ab2d11c32196633d4cb776b5bb25908ed212f\",\n  \"pins\" : [\n    {\n      \"identity\" : \"aboutwindow\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/CodeEditApp/AboutWindow\",\n      \"state\" : {\n        \"revision\" : \"79c7c01fb739d024a3ca07fe153a068339213baf\",\n        \"version\" : \"1.0.0\"\n      }\n    },\n    {\n      \"identity\" : \"anycodable\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/Flight-School/AnyCodable\",\n      \"state\" : {\n        \"revision\" : \"862808b2070cd908cb04f9aafe7de83d35f81b05\",\n        \"version\" : \"0.6.7\"\n      }\n    },\n    {\n      \"identity\" : \"codeeditkit\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/CodeEditApp/CodeEditKit.git\",\n      \"state\" : {\n        \"revision\" : \"ad28213a968586abb0cb21a8a56a3587227895f1\",\n        \"version\" : \"0.1.2\"\n      }\n    },\n    {\n      \"identity\" : \"codeeditlanguages\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/CodeEditApp/CodeEditLanguages.git\",\n      \"state\" : {\n        \"revision\" : \"331d5dbc5fc8513be5848fce8a2a312908f36a11\",\n        \"version\" : \"0.1.20\"\n      }\n    },\n    {\n      \"identity\" : \"codeeditsourceeditor\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/CodeEditApp/CodeEditSourceEditor\",\n      \"state\" : {\n        \"revision\" : \"ee0c00a2343903df9d6ef45ce53228aca8637369\",\n        \"version\" : \"0.15.1\"\n      }\n    },\n    {\n      \"identity\" : \"codeeditsymbols\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/CodeEditApp/CodeEditSymbols\",\n      \"state\" : {\n        \"revision\" : \"ae69712b08571c4469c2ed5cd38ad9f19439793e\",\n        \"version\" : \"0.2.3\"\n      }\n    },\n    {\n      \"identity\" : \"codeedittextview\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/CodeEditApp/CodeEditTextView.git\",\n      \"state\" : {\n        \"revision\" : \"d7ac3f11f22ec2e820187acce8f3a3fb7aa8ddec\",\n        \"version\" : \"0.12.1\"\n      }\n    },\n    {\n      \"identity\" : \"collectionconcurrencykit\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/johnsundell/collectionconcurrencykit\",\n      \"state\" : {\n        \"revision\" : \"b4f23e24b5a1bff301efc5e70871083ca029ff95\",\n        \"version\" : \"0.2.0\"\n      }\n    },\n    {\n      \"identity\" : \"concurrencyplus\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/ChimeHQ/ConcurrencyPlus\",\n      \"state\" : {\n        \"revision\" : \"8dc56499412a373d617d50d059116bccf44b9874\",\n        \"version\" : \"0.4.2\"\n      }\n    },\n    {\n      \"identity\" : \"fseventswrapper\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/Frizlab/FSEventsWrapper\",\n      \"state\" : {\n        \"revision\" : \"70bbea4b108221fcabfce8dbced8502831c0ae04\",\n        \"version\" : \"2.1.0\"\n      }\n    },\n    {\n      \"identity\" : \"grdb.swift\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/groue/GRDB.swift.git\",\n      \"state\" : {\n        \"revision\" : \"2cf6c756e1e5ef6901ebae16576a7e4e4b834622\",\n        \"version\" : \"6.29.3\"\n      }\n    },\n    {\n      \"identity\" : \"jsonrpc\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/ChimeHQ/JSONRPC\",\n      \"state\" : {\n        \"revision\" : \"c6ec759d41a76ac88fe7327c41a77d9033943374\",\n        \"version\" : \"0.9.0\"\n      }\n    },\n    {\n      \"identity\" : \"languageclient\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/ChimeHQ/LanguageClient\",\n      \"state\" : {\n        \"revision\" : \"4f28cc3cad7512470275f65ca2048359553a86f5\",\n        \"version\" : \"0.8.2\"\n      }\n    },\n    {\n      \"identity\" : \"languageserverprotocol\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/ChimeHQ/LanguageServerProtocol\",\n      \"state\" : {\n        \"revision\" : \"f7879c782c0845af9c576de7b8baedd946237286\",\n        \"version\" : \"0.14.0\"\n      }\n    },\n    {\n      \"identity\" : \"logstream\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/Wouter01/LogStream\",\n      \"state\" : {\n        \"revision\" : \"6f83694b2675dcf3b1cea0a52546ff4469c18282\",\n        \"version\" : \"1.3.0\"\n      }\n    },\n    {\n      \"identity\" : \"processenv\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/ChimeHQ/ProcessEnv\",\n      \"state\" : {\n        \"revision\" : \"83f1ebc9dd6fb1db0bd89a3fcae00488a0f3fdd9\",\n        \"version\" : \"1.0.0\"\n      }\n    },\n    {\n      \"identity\" : \"queue\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/mattmassicotte/Queue\",\n      \"state\" : {\n        \"revision\" : \"8d6f936097888f97011610ced40313655dc5948d\",\n        \"version\" : \"0.1.4\"\n      }\n    },\n    {\n      \"identity\" : \"rearrange\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/ChimeHQ/Rearrange\",\n      \"state\" : {\n        \"revision\" : \"f1d74e1642956f0300756ad8d1d64e9034857bc3\",\n        \"version\" : \"2.0.0\"\n      }\n    },\n    {\n      \"identity\" : \"semaphore\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/groue/Semaphore\",\n      \"state\" : {\n        \"revision\" : \"2543679282aa6f6c8ecf2138acd613ed20790bc2\",\n        \"version\" : \"0.1.0\"\n      }\n    },\n    {\n      \"identity\" : \"sparkle\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/sparkle-project/Sparkle.git\",\n      \"state\" : {\n        \"revision\" : \"2a98381dfe72e24bf593c5c06d2c4fc1763c3f19\",\n        \"version\" : \"2.3.0\"\n      }\n    },\n    {\n      \"identity\" : \"swift-async-algorithms\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-async-algorithms.git\",\n      \"state\" : {\n        \"revision\" : \"6ae9a051f76b81cc668305ceed5b0e0a7fd93d20\",\n        \"version\" : \"1.0.1\"\n      }\n    },\n    {\n      \"identity\" : \"swift-collections\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-collections.git\",\n      \"state\" : {\n        \"revision\" : \"9bf03ff58ce34478e66aaee630e491823326fd06\",\n        \"version\" : \"1.1.3\"\n      }\n    },\n    {\n      \"identity\" : \"swift-glob\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/davbeck/swift-glob\",\n      \"state\" : {\n        \"revision\" : \"07ba6f47d903a0b1b59f12ca70d6de9949b975d6\",\n        \"version\" : \"0.2.0\"\n      }\n    },\n    {\n      \"identity\" : \"swift-snapshot-testing\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/pointfreeco/swift-snapshot-testing.git\",\n      \"state\" : {\n        \"revision\" : \"bb0ea08db8e73324fe6c3727f755ca41a23ff2f4\",\n        \"version\" : \"1.14.2\"\n      }\n    },\n    {\n      \"identity\" : \"swift-syntax\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-syntax.git\",\n      \"state\" : {\n        \"revision\" : \"64889f0c732f210a935a0ad7cda38f77f876262d\",\n        \"version\" : \"509.1.1\"\n      }\n    },\n    {\n      \"identity\" : \"swiftlintplugin\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/lukepistrol/SwiftLintPlugin\",\n      \"state\" : {\n        \"revision\" : \"3780efccceaa87f17ec39638a9d263d0e742b71c\",\n        \"version\" : \"0.59.1\"\n      }\n    },\n    {\n      \"identity\" : \"swiftterm\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/thecoolwinter/SwiftTerm\",\n      \"state\" : {\n        \"branch\" : \"codeedit\",\n        \"revision\" : \"2f36f54742d3882e69ff009d084e8675b80934bd\"\n      }\n    },\n    {\n      \"identity\" : \"swifttreesitter\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/ChimeHQ/SwiftTreeSitter.git\",\n      \"state\" : {\n        \"revision\" : \"08ef81eb8620617b55b08868126707ad72bf754f\",\n        \"version\" : \"0.25.0\"\n      }\n    },\n    {\n      \"identity\" : \"swiftui-introspect\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/siteline/SwiftUI-Introspect.git\",\n      \"state\" : {\n        \"revision\" : \"807f73ce09a9b9723f12385e592b4e0aaebd3336\",\n        \"version\" : \"1.3.0\"\n      }\n    },\n    {\n      \"identity\" : \"textformation\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/ChimeHQ/TextFormation\",\n      \"state\" : {\n        \"revision\" : \"b1ce9a14bd86042bba4de62236028dc4ce9db6a1\",\n        \"version\" : \"0.9.0\"\n      }\n    },\n    {\n      \"identity\" : \"textstory\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/ChimeHQ/TextStory\",\n      \"state\" : {\n        \"revision\" : \"91df6fc9bd817f9712331a4a3e826f7bdc823e1d\",\n        \"version\" : \"0.9.1\"\n      }\n    },\n    {\n      \"identity\" : \"tree-sitter\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/tree-sitter/tree-sitter\",\n      \"state\" : {\n        \"revision\" : \"f2f197b6b27ce75c280c20f131d4f71e906b86f7\",\n        \"version\" : \"0.25.8\"\n      }\n    },\n    {\n      \"identity\" : \"welcomewindow\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/CodeEditApp/WelcomeWindow\",\n      \"state\" : {\n        \"revision\" : \"cbd5c0d6f432449e2a8618e2b24e4691acbfcc98\",\n        \"version\" : \"1.1.0\"\n      }\n    },\n    {\n      \"identity\" : \"zipfoundation\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/weichsel/ZIPFoundation\",\n      \"state\" : {\n        \"revision\" : \"02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0\",\n        \"version\" : \"0.9.19\"\n      }\n    }\n  ],\n  \"version\" : 3\n}\n"
  },
  {
    "path": "CodeEdit.xcodeproj/xcshareddata/xcschemes/CodeEdit.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"2610\"\n   version = \"1.7\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"B658FB2B27DA9E0F00EA4DBD\"\n               BuildableName = \"CodeEdit.app\"\n               BlueprintName = \"CodeEdit\"\n               ReferencedContainer = \"container:CodeEdit.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <TestPlans>\n         <TestPlanReference\n            reference = \"container:CodeEditTestPlan.xctestplan\"\n            default = \"YES\">\n         </TestPlanReference>\n      </TestPlans>\n      <Testables>\n         <TestableReference\n            skipped = \"NO\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"B658FB3C27DA9E1000EA4DBD\"\n               BuildableName = \"CodeEditTests.xctest\"\n               BlueprintName = \"CodeEditTests\"\n               ReferencedContainer = \"container:CodeEdit.xcodeproj\">\n            </BuildableReference>\n            <SkippedTests>\n               <Test\n                  Identifier = \"CodeEditUIUnitTests\">\n               </Test>\n               <Test\n                  Identifier = \"CodeEditUIUnitTests/testBranchPickerDark()\">\n               </Test>\n               <Test\n                  Identifier = \"CodeEditUIUnitTests/testBranchPickerLight()\">\n               </Test>\n               <Test\n                  Identifier = \"CodeEditUIUnitTests/testEffectViewDark()\">\n               </Test>\n               <Test\n                  Identifier = \"CodeEditUIUnitTests/testEffectViewLight()\">\n               </Test>\n               <Test\n                  Identifier = \"CodeEditUIUnitTests/testFontPickerViewDark()\">\n               </Test>\n               <Test\n                  Identifier = \"CodeEditUIUnitTests/testFontPickerViewLight()\">\n               </Test>\n               <Test\n                  Identifier = \"CodeEditUIUnitTests/testHelpButtonDark()\">\n               </Test>\n               <Test\n                  Identifier = \"CodeEditUIUnitTests/testHelpButtonLight()\">\n               </Test>\n               <Test\n                  Identifier = \"CodeEditUIUnitTests/testSegmentedControlDark()\">\n               </Test>\n               <Test\n                  Identifier = \"CodeEditUIUnitTests/testSegmentedControlLight()\">\n               </Test>\n               <Test\n                  Identifier = \"CodeEditUIUnitTests/testSegmentedControlProminentDark()\">\n               </Test>\n               <Test\n                  Identifier = \"CodeEditUIUnitTests/testSegmentedControlProminentLight()\">\n               </Test>\n               <Test\n                  Identifier = \"WelcomeModuleUnitTests\">\n               </Test>\n               <Test\n                  Identifier = \"WelcomeModuleUnitTests/testRecentJSFileDarkSnapshot()\">\n               </Test>\n               <Test\n                  Identifier = \"WelcomeModuleUnitTests/testRecentJSFileLightSnapshot()\">\n               </Test>\n               <Test\n                  Identifier = \"WelcomeModuleUnitTests/testRecentProjectItemDarkSnapshot()\">\n               </Test>\n               <Test\n                  Identifier = \"WelcomeModuleUnitTests/testRecentProjectItemLightSnapshot()\">\n               </Test>\n               <Test\n                  Identifier = \"WelcomeModuleUnitTests/testWelcomeActionViewDarkSnapshot()\">\n               </Test>\n               <Test\n                  Identifier = \"WelcomeModuleUnitTests/testWelcomeActionViewLightSnapshot()\">\n               </Test>\n            </SkippedTests>\n         </TestableReference>\n         <TestableReference\n            skipped = \"NO\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"B658FB4627DA9E1000EA4DBD\"\n               BuildableName = \"CodeEditUITests.xctest\"\n               BlueprintName = \"CodeEditUITests\"\n               ReferencedContainer = \"container:CodeEdit.xcodeproj\">\n            </BuildableReference>\n         </TestableReference>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"B658FB2B27DA9E0F00EA4DBD\"\n            BuildableName = \"CodeEdit.app\"\n            BlueprintName = \"CodeEdit\"\n            ReferencedContainer = \"container:CodeEdit.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"B658FB2B27DA9E0F00EA4DBD\"\n            BuildableName = \"CodeEdit.app\"\n            BlueprintName = \"CodeEdit\"\n            ReferencedContainer = \"container:CodeEdit.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "CodeEdit.xcodeproj/xcshareddata/xcschemes/OpenWithCodeEdit.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"2610\"\n   wasCreatedForAppExtension = \"YES\"\n   version = \"2.0\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\"\n      buildArchitectures = \"Automatic\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"2BE487EB28245162003F3F64\"\n               BuildableName = \"OpenWithCodeEdit.appex\"\n               BlueprintName = \"OpenWithCodeEdit\"\n               ReferencedContainer = \"container:CodeEdit.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"B658FB2B27DA9E0F00EA4DBD\"\n               BuildableName = \"CodeEdit.app\"\n               BlueprintName = \"CodeEdit\"\n               ReferencedContainer = \"container:CodeEdit.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      shouldAutocreateTestPlan = \"YES\">\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"\"\n      selectedLauncherIdentifier = \"Xcode.IDEFoundation.Launcher.PosixSpawn\"\n      launchStyle = \"0\"\n      askForAppToLaunch = \"Yes\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\"\n      launchAutomaticallySubstyle = \"2\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"B658FB2B27DA9E0F00EA4DBD\"\n            BuildableName = \"CodeEdit.app\"\n            BlueprintName = \"CodeEdit\"\n            ReferencedContainer = \"container:CodeEdit.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      askForAppToLaunch = \"Yes\"\n      launchAutomaticallySubstyle = \"2\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"B658FB2B27DA9E0F00EA4DBD\"\n            BuildableName = \"CodeEdit.app\"\n            BlueprintName = \"CodeEdit\"\n            ReferencedContainer = \"container:CodeEdit.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "CodeEditTestPlan.xctestplan",
    "content": "{\n  \"configurations\" : [\n    {\n      \"id\" : \"54F3AF04-2CB5-4C73-888F-75ABD9B93999\",\n      \"name\" : \"Configuration 1\",\n      \"options\" : {\n\n      }\n    }\n  ],\n  \"defaultOptions\" : {\n    \"targetForVariableExpansion\" : {\n      \"containerPath\" : \"container:CodeEdit.xcodeproj\",\n      \"identifier\" : \"B658FB2B27DA9E0F00EA4DBD\",\n      \"name\" : \"CodeEdit\"\n    }\n  },\n  \"testTargets\" : [\n    {\n      \"skippedTests\" : [\n        \"CodeEditUIUnitTests\",\n        \"CodeEditUIUnitTests\\/testBranchPickerDark()\",\n        \"CodeEditUIUnitTests\\/testBranchPickerLight()\",\n        \"CodeEditUIUnitTests\\/testEffectViewDark()\",\n        \"CodeEditUIUnitTests\\/testEffectViewLight()\",\n        \"CodeEditUIUnitTests\\/testFontPickerViewDark()\",\n        \"CodeEditUIUnitTests\\/testFontPickerViewLight()\",\n        \"CodeEditUIUnitTests\\/testHelpButtonDark()\",\n        \"CodeEditUIUnitTests\\/testHelpButtonLight()\",\n        \"CodeEditUIUnitTests\\/testSegmentedControlDark()\",\n        \"CodeEditUIUnitTests\\/testSegmentedControlLight()\",\n        \"CodeEditUIUnitTests\\/testSegmentedControlProminentDark()\",\n        \"CodeEditUIUnitTests\\/testSegmentedControlProminentLight()\",\n        \"RegistryTests\",\n        \"WelcomeModuleUnitTests\",\n        \"WelcomeModuleUnitTests\\/testRecentJSFileDarkSnapshot()\",\n        \"WelcomeModuleUnitTests\\/testRecentJSFileLightSnapshot()\",\n        \"WelcomeModuleUnitTests\\/testRecentProjectItemDarkSnapshot()\",\n        \"WelcomeModuleUnitTests\\/testRecentProjectItemLightSnapshot()\",\n        \"WelcomeModuleUnitTests\\/testWelcomeActionViewDarkSnapshot()\",\n        \"WelcomeModuleUnitTests\\/testWelcomeActionViewLightSnapshot()\"\n      ],\n      \"target\" : {\n        \"containerPath\" : \"container:CodeEdit.xcodeproj\",\n        \"identifier\" : \"B658FB3C27DA9E1000EA4DBD\",\n        \"name\" : \"CodeEditTests\"\n      }\n    },\n    {\n      \"target\" : {\n        \"containerPath\" : \"container:CodeEdit.xcodeproj\",\n        \"identifier\" : \"B658FB4627DA9E1000EA4DBD\",\n        \"name\" : \"CodeEditUITests\"\n      }\n    }\n  ],\n  \"version\" : 1\n}\n"
  },
  {
    "path": "CodeEditTests/Features/Acknowledgements/AcknowledgementsTests.swift",
    "content": "//\n//  AcknowledgementsTests.swift\n//  CodeEditTests\n//\n//  Created by Lukas Pistrol on 14.01.23.\n//\n\nimport XCTest\n@testable import CodeEdit\n\nfinal class AcknowledgementsTests: XCTestCase {\n\n    var model: AcknowledgementsViewModel!\n\n    override func setUpWithError() throws {\n        model = .init()\n    }\n\n    override func tearDownWithError() throws {\n        model = nil\n    }\n\n    func testAcknowledgementsNotEmpty() throws {\n        XCTAssertFalse(model.acknowledgements.isEmpty)\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/ActivityViewer/TaskNotificationHandlerTests.swift",
    "content": "//\n//  TaskNotificationHandlerTests.swift\n//  CodeEditTests\n//\n//  Created by Tommy Ludwig on 21.06.24.\n//\n\nimport XCTest\n@testable import CodeEdit\n\nfinal class TaskNotificationHandlerTests: XCTestCase {\n    var taskNotificationHandler: TaskNotificationHandler!\n\n    override func setUp() {\n        super.setUp()\n        taskNotificationHandler = TaskNotificationHandler()\n    }\n\n    override func tearDown() {\n        taskNotificationHandler = nil\n        super.tearDown()\n    }\n\n    func testCreateTask() {\n        let uuid = UUID().uuidString\n        let userInfo: [String: Any] = [\n            \"id\": uuid,\n            \"action\": \"create\",\n            \"title\": \"Task Title\"\n        ]\n        NotificationCenter.default.post(name: .taskNotification, object: nil, userInfo: userInfo)\n\n        let testExpectation = XCTestExpectation()\n        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {\n            XCTAssertEqual(self.taskNotificationHandler.notifications.first?.id, uuid)\n            testExpectation.fulfill()\n        }\n        wait(for: [testExpectation], timeout: 1)\n    }\n\n    func testCreateTaskWithPriority() {\n        let task1: [String: Any] = [\n            \"id\": UUID().uuidString,\n            \"action\": \"create\",\n            \"title\": \"Task Title\"\n        ]\n        NotificationCenter.default.post(name: .taskNotification, object: nil, userInfo: task1)\n\n        let task2: [String: Any] = [\n            \"id\": UUID().uuidString,\n            \"action\": \"createWithPriority\",\n            \"title\": \"Priority Task Title\"\n        ]\n        NotificationCenter.default.post(name: .taskNotification, object: nil, userInfo: task2)\n\n        let testExpectation = XCTestExpectation()\n        DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {\n            XCTAssertEqual(self.taskNotificationHandler.notifications.first?.title, \"Priority Task Title\")\n            testExpectation.fulfill()\n        }\n        wait(for: [testExpectation], timeout: 1)\n    }\n\n    func testUpdateTask() {\n        let uuid = UUID().uuidString\n        let taskInfo: [String: Any] = [\n            \"id\": uuid,\n            \"action\": \"create\",\n            \"title\": \"Task Title\"\n        ]\n        NotificationCenter.default.post(name: .taskNotification, object: nil, userInfo: taskInfo)\n\n        let taskUpdateInfo: [String: Any] = [\n            \"id\": uuid,\n            \"action\": \"update\",\n            \"title\": \"Updated Task Title\"\n        ]\n        NotificationCenter.default.post(name: .taskNotification, object: nil, userInfo: taskUpdateInfo)\n\n        let testExpectation = XCTestExpectation()\n        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {\n            XCTAssertEqual(self.taskNotificationHandler.notifications.first?.title, \"Updated Task Title\")\n            testExpectation.fulfill()\n        }\n        wait(for: [testExpectation], timeout: 1)\n    }\n\n    func testDeleteTask() {\n        let uuid = UUID().uuidString\n        let createUserInfo: [String: Any] = [\n            \"id\": uuid,\n            \"action\": \"create\",\n            \"title\": \"Task Title\"\n        ]\n        NotificationCenter.default.post(name: .taskNotification, object: nil, userInfo: createUserInfo)\n        let deleteUserInfo: [String: Any] = [\n            \"id\": uuid,\n            \"action\": \"delete\"\n        ]\n        NotificationCenter.default.post(name: .taskNotification, object: nil, userInfo: deleteUserInfo)\n\n        let testExpectation = XCTestExpectation()\n        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {\n            XCTAssertTrue(self.taskNotificationHandler.notifications.isEmpty)\n            testExpectation.fulfill()\n        }\n        wait(for: [testExpectation], timeout: 1)\n    }\n\n    func testDeleteTaskWithDelay() {\n        let uuid = UUID().uuidString\n        let createUserInfo: [String: Any] = [\n            \"id\": uuid,\n            \"action\": \"create\",\n            \"title\": \"Task Title\"\n        ]\n        NotificationCenter.default.post(name: .taskNotification, object: nil, userInfo: createUserInfo)\n        let deleteUserInfo: [String: Any] = [\n            \"id\": uuid,\n            \"action\": \"deleteWithDelay\",\n            \"delay\": 0.2\n        ]\n        NotificationCenter.default.post(name: .taskNotification, object: nil, userInfo: deleteUserInfo)\n\n        let testExpectation = XCTestExpectation()\n        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {\n            XCTAssertFalse(self.taskNotificationHandler.notifications.isEmpty)\n        }\n        DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {\n            XCTAssertTrue(self.taskNotificationHandler.notifications.isEmpty)\n            testExpectation.fulfill()\n        }\n        wait(for: [testExpectation], timeout: 1)\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/CodeEditUI/CodeEditUITests-Bridging-Header.h",
    "content": "//\n//  CodeEditUITests-Bridging-Header.h\n//  CodeEditUITests\n//\n//  Created by Matthijs Eikelenboom on 29/11/2022.\n//\n\n#ifndef CodeEditUITests_Bridging_Header_h\n#define CodeEditUITests_Bridging_Header_h\n\n\n#endif /* CodeEditUITests_Bridging_Header_h */\n"
  },
  {
    "path": "CodeEditTests/Features/CodeEditUI/CodeEditUITests.swift",
    "content": "//\n//  UnitTests.swift\n//  CodeEditModules/CodeEditUITests\n//\n//  Created by Lukas Pistrol on 19.04.22.\n//\n\n@testable import CodeEdit\nimport Foundation\nimport SnapshotTesting\nimport SwiftUI\nimport XCTest\n\nfinal class CodeEditUIUnitTests: XCTestCase {\n\n    // MARK: Help Button\n\n    func testHelpButtonLight() throws {\n        let view = HelpButton(action: {})\n        let hosting = NSHostingView(rootView: view)\n        hosting.frame = CGRect(origin: .zero, size: .init(width: 40, height: 40))\n        hosting.appearance = .init(named: .aqua)\n        assertSnapshot(matching: hosting, as: .image(size: .init(width: 40, height: 40)))\n    }\n\n    func testHelpButtonDark() throws {\n        let view = HelpButton(action: {})\n        let hosting = NSHostingView(rootView: view)\n        hosting.appearance = .init(named: .darkAqua)\n        hosting.frame = CGRect(origin: .zero, size: .init(width: 40, height: 40))\n        assertSnapshot(matching: hosting, as: .image)\n    }\n\n    // MARK: Segmented Control\n\n    func testSegmentedControlLight() throws {\n        let view = SegmentedControl(.constant(0), options: [\"Opt1\", \"Opt2\"])\n        let hosting = NSHostingView(rootView: view)\n        hosting.appearance = .init(named: .aqua)\n        hosting.frame = CGRect(origin: .zero, size: .init(width: 100, height: 30))\n        assertSnapshot(matching: hosting, as: .image)\n    }\n\n    func testSegmentedControlDark() throws {\n        let view = SegmentedControl(.constant(0), options: [\"Opt1\", \"Opt2\"])\n        let hosting = NSHostingView(rootView: view)\n        hosting.appearance = .init(named: .darkAqua)\n        hosting.frame = CGRect(origin: .zero, size: .init(width: 100, height: 30))\n        assertSnapshot(matching: hosting, as: .image)\n    }\n\n    func testSegmentedControlProminentLight() throws {\n        let view = SegmentedControl(.constant(0), options: [\"Opt1\", \"Opt2\"], prominent: true)\n        let hosting = NSHostingView(rootView: view)\n        hosting.appearance = .init(named: .aqua)\n        hosting.frame = CGRect(origin: .zero, size: .init(width: 100, height: 30))\n        assertSnapshot(matching: hosting, as: .image)\n    }\n\n    func testSegmentedControlProminentDark() throws {\n        let view = SegmentedControl(.constant(0), options: [\"Opt1\", \"Opt2\"], prominent: true)\n        let hosting = NSHostingView(rootView: view)\n        hosting.appearance = .init(named: .darkAqua)\n        hosting.frame = CGRect(origin: .zero, size: .init(width: 100, height: 30))\n        assertSnapshot(matching: hosting, as: .image)\n    }\n\n    // MARK: EffectView\n\n    func testEffectViewLight() throws {\n        let view = EffectView()\n        let hosting = NSHostingView(rootView: view)\n        hosting.appearance = .init(named: .aqua)\n        hosting.frame = CGRect(origin: .zero, size: .init(width: 20, height: 20))\n        assertSnapshot(matching: hosting, as: .image)\n    }\n\n    func testEffectViewDark() throws {\n        let view = EffectView()\n        let hosting = NSHostingView(rootView: view)\n        hosting.appearance = .init(named: .darkAqua)\n        hosting.frame = CGRect(origin: .zero, size: .init(width: 20, height: 20))\n        assertSnapshot(matching: hosting, as: .image)\n    }\n\n    // MARK: ToolbarBranchPicker\n\n    func testBranchPickerLight() throws {\n        let view = ToolbarBranchPicker(\n            workspaceFileManager: nil\n        )\n        let hosting = NSHostingView(rootView: view)\n        hosting.appearance = .init(named: .aqua)\n        hosting.frame = CGRect(origin: .zero, size: .init(width: 100, height: 50))\n        assertSnapshot(matching: hosting, as: .image)\n    }\n\n    func testBranchPickerDark() throws {\n        let view = ToolbarBranchPicker(\n            workspaceFileManager: nil\n        )\n        let hosting = NSHostingView(rootView: view)\n        hosting.appearance = .init(named: .darkAqua)\n        hosting.frame = CGRect(origin: .zero, size: .init(width: 100, height: 50))\n        assertSnapshot(matching: hosting, as: .image)\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/CodeFile/CodeFileDocument+UTTypeTests.swift",
    "content": "//\n//  CodeFileDocument+UTTypeTests.swift\n//  CodeEditTests\n//\n//  Created by Axel Martinez on 23/6/24.\n//\n\nimport XCTest\n\n@testable import CodeEdit\n\nfinal class UTTypeTests: XCTestCase {\n    private var document: CodeFileDocument!\n\n    override func setUp() {\n        document = .init()\n    }\n\n    func testTextFileByContent() {\n        document.content = NSTextStorage(string: \"Some text content\")\n        XCTAssertEqual(document.utType, .text)\n    }\n\n    func testJSONFile() {\n        document.fileType = \"public.json\"\n        XCTAssertEqual(document.utType, .json)\n    }\n\n    func testTextFileByExtension() {\n        document.fileType = \"public.python-script\"\n        XCTAssertEqual(document.utType, .pythonScript)\n    }\n\n    func testPdfFile() {\n        document.fileType = \"com.adobe.pdf\"\n        XCTAssertEqual(document.utType, .pdf)\n    }\n\n    func testImageFile() {\n        document.fileType = \"public.image\"\n        XCTAssertEqual(document.utType, .image)\n    }\n\n    func testPngFile() {\n        document.fileType = \"public.png\"\n        XCTAssertEqual(document.utType, .png)\n    }\n\n    func testAudioFile() {\n        document.fileType = \"public.audio\"\n        XCTAssertEqual(document.utType, .audio)\n    }\n\n    func testMp3File() {\n        document.fileType = \"public.mp3\"\n        XCTAssertEqual(document.utType, .mp3)\n    }\n\n    func testVideoFile() {\n        document.fileType = \"public.video\"\n        XCTAssertEqual(document.utType, .video)\n    }\n\n    func testMpeg4File() {\n        document.fileType = \"public.mpeg-4\"\n        XCTAssertEqual(document.utType, .mpeg4Movie)\n    }\n\n    func testUnknownFileType() {\n        document.fileType = \"unknown\"\n        XCTAssertNil(document.utType)\n    }\n\n    func testEmptyFileTypeAndContent() {\n        XCTAssertNil(document.utType)\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/CodeFile/CodeFileDocumentTests.swift",
    "content": "//\n//  CodeFileDocumentTests.swift\n//  CodeEditModules/CodeFileTests\n//\n//  Created by Marco Carnevali on 18/03/22.\n//\n\nimport Foundation\nimport SwiftUI\nimport Testing\n@testable import CodeEdit\n\n@Suite\nstruct CodeFileDocumentTests {\n    let defaultString = \"func test() { }\"\n\n    private func withFile(_ operation: (URL) throws -> Void) throws {\n        try withTempDir { dir in\n            let fileURL = dir.appending(path: \"file.swift\")\n            try operation(fileURL)\n        }\n    }\n\n    private func withCodeFile(_ operation: (CodeFileDocument) throws -> Void) throws {\n        try withFile { fileURL in\n            try defaultString.write(to: fileURL, atomically: true, encoding: .utf8)\n            let codeFile = try CodeFileDocument(contentsOf: fileURL, ofType: \"public.source-code\")\n            try operation(codeFile)\n        }\n    }\n\n    @Test\n    func testLoadUTF8Encoding() throws {\n        try withFile { fileURL in\n            try defaultString.write(to: fileURL, atomically: true, encoding: .utf8)\n            let codeFile = try CodeFileDocument(\n                for: fileURL,\n                withContentsOf: fileURL,\n                ofType: \"public.source-code\"\n            )\n            #expect(codeFile.content?.string == defaultString)\n            #expect(codeFile.sourceEncoding == .utf8)\n        }\n    }\n\n    @Test\n    func testWriteUTF8Encoding() throws {\n        try withFile { fileURL in\n            let codeFile = CodeFileDocument()\n            codeFile.content = NSTextStorage(string: defaultString)\n            codeFile.sourceEncoding = .utf8\n            try codeFile.write(to: fileURL, ofType: \"public.source-code\")\n\n            let data = try Data(contentsOf: fileURL)\n            var nsString: NSString?\n            let fileEncoding = NSString.stringEncoding(\n                for: data,\n                encodingOptions: [\n                    .suggestedEncodingsKey: FileEncoding.allCases.map { $0.nsValue },\n                    .useOnlySuggestedEncodingsKey: true\n                ],\n                convertedString: &nsString,\n                usedLossyConversion: nil\n            )\n\n            #expect(codeFile.content?.string as NSString? == nsString)\n            #expect(fileEncoding == NSUTF8StringEncoding)\n        }\n    }\n\n    @Test\n    func ignoresExternalUpdatesWithOutstandingChanges() throws {\n        try withCodeFile { codeFile in\n            // Mark the file dirty\n            codeFile.updateChangeCount(.changeDone)\n\n            // Update the modification date\n            try \"different contents\".write(to: codeFile.fileURL!, atomically: true, encoding: .utf8)\n\n            // Tell the file the disk representation changed\n            codeFile.presentedItemDidChange()\n\n            // The file should not have reloaded\n            #expect(codeFile.content?.string == defaultString)\n            #expect(codeFile.isDocumentEdited == true)\n        }\n    }\n\n    @Test\n    func loadsExternalUpdatesWithNoOutstandingChanges() throws {\n        try withCodeFile { codeFile in\n            // Update the modification date\n            try \"different contents\".write(to: codeFile.fileURL!, atomically: true, encoding: .utf8)\n\n            // Tell the file the disk representation changed\n            codeFile.presentedItemDidChange()\n\n            // The file should have reloaded (it was clean)\n            #expect(codeFile.content?.string == \"different contents\")\n            #expect(codeFile.isDocumentEdited == false)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/Documents/DocumentsUnitTests.swift",
    "content": "//\n//  DocumentsUnitTests.swift\n//  CodeEditTests\n//\n//  Created by YAPRYNTSEV Aleksey on 31.12.2022.\n//\n\nimport XCTest\n@testable import CodeEdit\n\n@MainActor\nfinal class DocumentsUnitTests: XCTestCase {\n    // Properties\n    private var splitViewController: CodeEditSplitViewController!\n    private var hapticFeedbackPerformerMock: NSHapticFeedbackPerformerMock!\n    private var navigatorViewModel: NavigatorAreaViewModel!\n    private var window: NSWindow!\n    private var workspace = WorkspaceDocument()\n\n    // MARK: - Lifecycle\n\n    override func setUp() {\n        super.setUp()\n        hapticFeedbackPerformerMock = NSHapticFeedbackPerformerMock()\n        navigatorViewModel = .init()\n        workspace.taskManager = TaskManager(workspaceSettings: CEWorkspaceSettingsData(), workspaceURL: nil)\n        window = NSWindow()\n        splitViewController = .init(\n            workspace: workspace,\n            navigatorViewModel: navigatorViewModel,\n            windowRef: window,\n            hapticPerformer: hapticFeedbackPerformerMock\n        )\n        splitViewController.viewDidLoad()\n    }\n\n    override func tearDown() {\n        splitViewController = nil\n        super.tearDown()\n    }\n\n    // MARK: - Tests\n\n    func testSplitViewHasItems() {\n        XCTAssertGreaterThan(splitViewController.splitViewItems.count, 0, \"Split controller did not set up correctly.\")\n    }\n\n    func testSplitViewControllerSnappedWhenWidthInAppropriateRange() {\n        for _ in 0..<10 {\n            // Given\n            let position = CGFloat.random(\n                in: (CodeEditSplitViewController.minSnapWidth...CodeEditSplitViewController.maxSnapWidth)\n            )\n\n            // When\n            let result = splitViewController.splitView(\n                splitViewController.splitView,\n                constrainSplitPosition: .init(position),\n                ofSubviewAt: .zero\n            )\n\n            // Then\n            XCTAssertEqual(result, CodeEditSplitViewController.snapWidth)\n        }\n    }\n\n    func testSplitViewControllerStopSnappedWhenWidthIsLowerAppropriateRange() {\n        for _ in 0..<10 {\n            // Given\n            let position = CGFloat.random(in: 0..<(CodeEditSplitViewController.minSidebarWidth / 2))\n\n            // When\n            let result = splitViewController.splitView(\n                splitViewController.splitView,\n                constrainSplitPosition: .init(position),\n                ofSubviewAt: .zero\n            )\n\n            // Then\n            XCTAssertEqual(result, .zero)\n        }\n    }\n\n    func testSplitViewControllerStopSnappedWhenWidthIsHigherAppropriateRange() {\n        for _ in 0..<10 {\n            // Given\n            let position = CGFloat.random(in: (CodeEditSplitViewController.maxSnapWidth...500))\n\n            // When\n            let result = splitViewController.splitView(\n                splitViewController.splitView,\n                constrainSplitPosition: .init(position),\n                ofSubviewAt: .zero\n            )\n\n            // Then\n            XCTAssertEqual(result, .init(position))\n        }\n    }\n\n    // Test moving from collapsed to uncollapsed makes a haptic.\n    func testSplitViewControllerProducedHapticFeedback() {\n        for _ in 0..<10 {\n            // Given\n            splitViewController.splitViewItems.first?.isCollapsed = true\n            let position = CGFloat.random(\n                in: (CodeEditSplitViewController.minSidebarWidth / 2)...CodeEditSplitViewController.minSidebarWidth\n            )\n\n            // When\n            _ = splitViewController.splitView(\n                splitViewController.splitView,\n                constrainSplitPosition: .init(position),\n                ofSubviewAt: .zero\n            )\n\n            // Then\n            XCTAssertTrue(hapticFeedbackPerformerMock.invokedPerform)\n            XCTAssertEqual(hapticFeedbackPerformerMock.invokedPerformCount, 1)\n            hapticFeedbackPerformerMock.reset()\n        }\n    }\n\n    func testSplitViewControllerProducedHapticFeedbackOnceWhenPlentyChangesOccur() {\n        for _ in 0..<10 {\n            // Given\n            splitViewController.splitViewItems.first?.isCollapsed = true\n            let firstPosition = CGFloat.random(in: 0..<(CodeEditSplitViewController.minSidebarWidth / 2))\n            let secondPosition = CGFloat.random(\n                in: (CodeEditSplitViewController.minSidebarWidth / 2)...CodeEditSplitViewController.minSidebarWidth\n            )\n\n            // When\n            [firstPosition, secondPosition].forEach { position in\n                _ = splitViewController.splitView(\n                    splitViewController.splitView,\n                    constrainSplitPosition: .init(position),\n                    ofSubviewAt: .zero\n                )\n            }\n\n            // Then\n            XCTAssertTrue(hapticFeedbackPerformerMock.invokedPerform)\n            XCTAssertEqual(hapticFeedbackPerformerMock.invokedPerformCount, 1)\n            hapticFeedbackPerformerMock.reset()\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/Documents/Indexer/AsyncIndexingTests.swift",
    "content": "//\n//  AsyncIndexingTests.swift\n//  CodeEditTests\n//\n//  Created by Tommy Ludwig on 09.12.23.\n//\n\nimport XCTest\n@testable import CodeEdit\n\nfinal class AsyncIndexingTests: XCTestCase {\n    func testAddDocuments() {\n        guard let index = SearchIndexer.Memory.create() else {\n            XCTFail(\"Failed to create an index\")\n            return\n        }\n\n        let asyncManager = SearchIndexer.AsyncManager(index: index)\n        let expectation = XCTestExpectation(description: \"Async operations completed\")\n        let tempFile1 = TemporaryFile().url\n        let tempFile2 = TemporaryFile().url\n\n        Task {\n            let results = await asyncManager.addFiles(urls: [tempFile1, tempFile2])\n            XCTAssertEqual(results.count, 2, \"Unexpected indexing results.\")\n            asyncManager.index.flush()\n            let documents = asyncManager.index.documents()\n            XCTAssertEqual(documents.count, 2)\n            expectation.fulfill()\n        }\n\n        wait(for: [expectation], timeout: 2)\n    }\n\n    func testSearchDocuments() {\n        guard let index = SearchIndexer.Memory.create() else {\n            XCTFail(\"Failed to create an index\")\n            return\n        }\n\n        let asyncManager = SearchIndexer.AsyncManager(index: index)\n        let expectation = XCTestExpectation(description: \"Async operations completed\")\n\n        let tempFile1 = SearchIndexer.AsyncManager.TextFile(\n            url: TemporaryFile().url,\n            text: \"Itaque ratione asperiores.\"\n        )\n        let tempFile2 = SearchIndexer.AsyncManager.TextFile(\n            url: TemporaryFile().url,\n            text: \"Perspiciatis perspiciatis rerum ex asperiores.\"\n        )\n\n        Task {\n            var searchResults = [URL]()\n            let results = await asyncManager.addText(files: [tempFile1, tempFile2])\n            XCTAssertEqual(results.count, 2, \"Unexpected indexing results.\")\n            asyncManager.index.flush()\n            let searchStream = await asyncManager.search(query: \"asperiores\", 10)\n            for try await result in searchStream {\n                let urls: [(URL, Float)] = result.results.compactMap {\n                    ($0.url, $0.score)\n                }\n\n                for (url, _) in urls {\n                    searchResults.append(url)\n                }\n            }\n\n            XCTAssertEqual(searchResults.count, 2)\n            expectation.fulfill()\n        }\n\n        wait(for: [expectation], timeout: 2)\n    }\n\n}\n"
  },
  {
    "path": "CodeEditTests/Features/Documents/Indexer/MemoryIndexingTests.swift",
    "content": "//\n//  MemoryIndexing.swift\n//  CodeEditTests\n//\n//  Created by Tommy Ludwig on 08.12.23.\n//\n\nimport XCTest\n@testable import CodeEdit\n\nfinal class MemoryIndexingTests: XCTestCase {\n    func testIndexFile() {\n        guard let index = SearchIndexer.Memory.create() else {\n            XCTFail(\"Failed to create an index\")\n            return\n        }\n\n        let filePath = TemporaryFile().url\n\n        let indexResults = index.addFileWithText(filePath, text: \"Hello, World!\")\n        XCTAssert(indexResults)\n    }\n\n    func testIndexFiles() {\n        guard let index = SearchIndexer.Memory.create() else {\n            XCTFail(\"Failed to create an index\")\n            return\n        }\n\n        let document1 = TemporaryFile().url\n        let document2 = TemporaryFile().url\n\n        var indexResults = index.addFileWithText(document1, text: \"fileContent\")\n        XCTAssert(indexResults)\n        indexResults = index.addFileWithText(document2, text: \"\")\n        XCTAssert(indexResults)\n        index.flush()\n        let res = index.cleanUp()\n        XCTAssertEqual(res, 1)\n    }\n\n    func testIndexFolder() {\n        guard let index = SearchIndexer.Memory.create() else {\n            XCTFail(\"Failed to create an index\")\n            return\n        }\n\n        let folder = TempFolderManager()\n        folder.createCustomFolder()\n        folder.createFiles()\n\n        let indexResults = index.addFolderContent(folderURL: folder.customFolderURL)\n        XCTAssertEqual(indexResults.count, 2)\n\n        index.flush()\n\n        let searchResults = index.search(\"file\")\n        XCTAssertEqual(searchResults.count, 2, \"Unexpected search results\")\n    }\n\n    func testIndexCleanUp() {\n        guard let index = SearchIndexer.Memory.create() else {\n            XCTFail(\"Failed to create an index\")\n            return\n        }\n\n        let document1 = TemporaryFile().url\n        let document2 = TemporaryFile().url\n\n        var indexResults = index.addFileWithText(document1, text: \"fileContent\")\n        XCTAssert(indexResults)\n        indexResults = index.addFileWithText(document2, text: \"\")\n        XCTAssert(indexResults)\n        index.flush()\n        let res = index.cleanUp()\n        XCTAssertEqual(res, 1)\n    }\n\n    func testCloseIndex() {\n        guard let index = SearchIndexer.Memory.create() else {\n            XCTFail(\"Failed to create an index\")\n            return\n        }\n\n        let filePath = TemporaryFile().url\n\n        let indexResults = index.addFileWithText(filePath, text: \"Hello, World!\")\n        XCTAssert(indexResults)\n\n        index.close()\n\n        let closedIndexResults = index.addFileWithText(filePath, text: \"Hello, World\")\n        XCTAssertEqual(closedIndexResults, false)\n    }\n\n    func testDocumentIsIndex() {\n        guard let index = SearchIndexer.Memory.create() else {\n            XCTFail(\"Failed to create an index\")\n            return\n        }\n\n        let filePath = TemporaryFile().url\n\n        let indexResults = index.addFileWithText(filePath, text: \"Hello, World!\")\n        XCTAssert(indexResults)\n\n        let isIndexed = index.documentIndexed(filePath)\n        XCTAssertEqual(isIndexed, false)\n\n        index.flush()\n        let isIndexedAfterFlush = index.documentIndexed(filePath)\n        XCTAssert(isIndexedAfterFlush)\n    }\n\n    func testSaveAndLoad() {\n        guard let index = SearchIndexer.Memory.create() else {\n            XCTFail(\"Failed to create an index\")\n            return\n        }\n\n        let textFilePath = TemporaryFile().url\n\n        XCTAssertTrue(index.addFileWithText(textFilePath, text: \"Illum assumenda iure earum dolorum fugit.\"))\n\n        index.flush()\n\n        let searchResults = index.search(\"earum\")\n        XCTAssertEqual(1, searchResults.count)\n        XCTAssertEqual(searchResults[0].url, textFilePath)\n\n        // Save the current index.\n        let savedIndex = index.getAsData()\n        XCTAssertNotNil(savedIndex, \"Failed to save the index.\")\n\n        // Close the index, i.e. the index gets deallocated form memory.\n        index.close()\n\n        // Load the saved index\n        guard let loadedIndex = SearchIndexer.Memory(data: savedIndex!) else {\n            XCTFail(\"Failed to create an index\")\n            return\n        }\n\n        let savedIndexResult = loadedIndex.search(\"earum\")\n        XCTAssertEqual(savedIndexResult.count, 1)\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/Documents/Indexer/MemorySearchTests.swift",
    "content": "//\n//  MemoryIndexSearch.swift\n//  CodeEditTests\n//\n//  Created by Tommy Ludwig on 08.12.23.\n//\n\nimport XCTest\n@testable import CodeEdit\n\nfinal class MemoryIndexSearchTests: XCTestCase {\n    func testIndexFileSearch() {\n        guard let index = SearchIndexer.Memory.create() else {\n            XCTFail(\"Failed to create an index\")\n            return\n        }\n\n        let filePath = TemporaryFile().url\n\n        let indexResults = index.addFileWithText(filePath, text: \"Hello, World!\")\n        XCTAssert(indexResults)\n        index.flush()\n        let progressiveSearch = index.progressiveSearch(query: \"hello\")\n        let progressiveSearchResults = progressiveSearch.getNextSearchResultsChunk(limit: 10)\n        XCTAssertEqual(progressiveSearchResults.results.count, 1)\n    }\n\n    func testIndexFolderSearch() {\n        guard let index = SearchIndexer.Memory.create() else {\n            XCTFail(\"Failed to create an index\")\n            return\n        }\n\n        let folder = TempFolderManager()\n        folder.createCustomFolder()\n        folder.createFiles()\n\n        let indexResults = index.addFolderContent(folderURL: folder.customFolderURL)\n        XCTAssertEqual(indexResults.count, 2)\n    }\n\n    func testIndexFileWildCardSearch() {\n        guard let index = SearchIndexer.Memory.create() else {\n            XCTFail(\"Failed to create an index\")\n            return\n        }\n\n        let filePath = TemporaryFile().url\n\n        let indexResults = index.addFileWithText(filePath, text: \"Hello, World!\")\n        XCTAssert(indexResults)\n        index.flush()\n        let progressiveSearch = index.progressiveSearch(query: \"*ll*\")\n        let progressiveSearchResults = progressiveSearch.getNextSearchResultsChunk(limit: 10)\n        XCTAssertEqual(progressiveSearchResults.results.count, 1)\n    }\n\n    func testIndexRemoveDocument() {\n        guard let index = SearchIndexer.Memory.create() else {\n            XCTFail(\"Failed to create an index\")\n            return\n        }\n\n        let document1 = TemporaryFile().url\n        let document2 = TemporaryFile().url\n        XCTAssertTrue(index.addFileWithText(document1, text: \"Hello, World!\"), \"Failed to add docs to index.\")\n        XCTAssertTrue(index.addFileWithText(document2, text: \"Hello, Swift!\"), \"Failed to add docs to index.\")\n\n        index.flush()\n\n        let documents = index.documents()\n        XCTAssertEqual(documents.count, 2)\n\n        let searchResults = index.search(\"Hello\")\n        XCTAssertEqual(searchResults.count, 2, \"Unexpected search results.\")\n\n        let removeResult = index.removeDocument(url: document1)\n        XCTAssertTrue(removeResult, \"Failed to remove documents.\")\n\n        index.flush()\n\n        let searchResultsAfterFlush = index.search(\"Hello\")\n        XCTAssertEqual(searchResultsAfterFlush.count, 1, \"Unexpected search results.\")\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/Documents/Indexer/TemporaryFile.swift",
    "content": "//\n//  TemporaryFile.swift\n//  CodeEditTests\n//\n//  Created by Tommy Ludwig on 08.12.23.\n//\n\nimport Foundation\n\n/// A utility class representing a temporary file with automatic cleanup upon deallocation.\n///\n/// This class provides a convenient way to create a temporary\n/// file with a unique name and automatically removes the file when the `TemporaryFile` instance is deallocated.\n///\n/// Example usage:\n/// ```swift\n/// let tempFile = TemporaryFile()\n/// // Use tempFile.url for file operations\n/// // The file will be automatically removed when tempFile is no longer in use.\n/// ```\nclass TemporaryFile {\n    /// The URL of the temporary file.\n    let url: URL = {\n        let folder = NSTemporaryDirectory()\n        let name = UUID().uuidString\n\n        return NSURL.fileURL(withPathComponents: [folder, name])! as URL\n    }()\n\n    /// Deinitializes the `TemporaryFile` instance and removes the associated temporary file from the filesystem.\n    deinit {\n        try? FileManager.default.removeItem(at: url)\n    }\n}\n\n/// A utility class for managing a temporary folder with customizable files.\n///\n/// The `TempFolderManager` class facilitates the creation, management, \n/// and cleanup of a temporary folder with associated files.\n///\n/// Example usage:\n/// ```swift\n/// let folderManager = TempFolderManager()\n/// folderManager.createCustomFolder()\n/// folderManager.createFiles()\n/// // Use files within the custom folder as needed\n/// // The folder and files will be automatically cleaned up upon the `TempFolderManager` instance deinitialization.\n/// ```\nclass TempFolderManager {\n    let temporaryDirectoryURL: URL\n    let customFolderURL: URL\n\n    init() {\n        self.temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory())\n        self.customFolderURL = temporaryDirectoryURL.appending(path: \"TestingFolder\")\n    }\n\n    deinit {\n        cleanup()\n    }\n\n    func createCustomFolder() {\n        do {\n            try FileManager.default.createDirectory(\n                at: customFolderURL,\n                withIntermediateDirectories: true,\n                attributes: nil\n            )\n        } catch {\n            print(\"Error creating directory: \\(error)\")\n        }\n    }\n\n    func createFiles() {\n        let file1URL = customFolderURL.appending(path: \"file1.txt\")\n        let file2URL = customFolderURL.appending(path: \"file2.txt\")\n\n        let file1Content = \"This is file 1\"\n        let file2Content = \"This is file 2\"\n\n        do {\n            try file1Content.write(to: file1URL, atomically: true, encoding: .utf8)\n            try file2Content.write(to: file2URL, atomically: true, encoding: .utf8)\n        } catch {\n            print(\"Error writing to file: \\(error)\")\n        }\n    }\n\n    func cleanup() {\n        do {\n            let file1URL = customFolderURL.appending(path: \"file1.txt\")\n            let file2URL = customFolderURL.appending(path: \"file2.txt\")\n\n            try FileManager.default.removeItem(at: file1URL)\n            try FileManager.default.removeItem(at: file2URL)\n            try FileManager.default.removeItem(at: customFolderURL)\n        } catch {\n            print(\"Error removing item: \\(error)\")\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/Documents/Mocks/NSHapticFeedbackPerformerMock.swift",
    "content": "//\n//  NSHapticFeedbackPerformerMock.swift\n//  CodeEditTests\n//\n//  Created by YAPRYNTSEV Aleksey on 31.12.2022.\n//\n\nimport Cocoa\n\nfinal class NSHapticFeedbackPerformerMock: NSObject, NSHapticFeedbackPerformer {\n\n    var invokedPerform: Bool {\n        invokedPerformCount > 0\n    }\n    var invokedPerformCount = 0\n\n    func perform(\n        _ pattern: NSHapticFeedbackManager.FeedbackPattern,\n        performanceTime: NSHapticFeedbackManager.PerformanceTime\n    ) {\n        invokedPerformCount += 1\n    }\n\n    func reset() {\n        invokedPerformCount = 0\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/Documents/WorkspaceDocument+SearchState+FindAndReplaceTests.swift",
    "content": "//\n//  WorkspaceDocument+SearchState+FindAndReplaceTests.swift\n//  CodeEditTests\n//\n//  Created by Tommy Ludwig on 26.01.24.\n//\n\nimport XCTest\n@testable import CodeEdit\n\n@MainActor\nfinal class FindAndReplaceTests: XCTestCase { // swiftlint:disable:this type_body_length\n    private var directory: URL!\n    private var files: [CEWorkspaceFile] = []\n    private var mockWorkspace: WorkspaceDocument!\n    private var searchState: WorkspaceDocument.SearchState!\n\n    private var folder1File: CEWorkspaceFile?\n    private var folder2File: CEWorkspaceFile?\n\n    // MARK: - Setup\n    /// A mock WorkspaceDocument is created\n    /// 3 mock files are added to the index\n    /// which will be removed in the teardown function\n    override func setUp() async throws {\n        directory = try FileManager.default.url(\n            for: .developerApplicationDirectory,\n            in: .userDomainMask,\n            appropriateFor: nil,\n            create: true\n        )\n        .appending(path: \"CodeEdit\", directoryHint: .isDirectory)\n        .appending(path: \"WorkspaceClientTests\", directoryHint: .isDirectory)\n        try? FileManager.default.removeItem(at: directory)\n        try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)\n\n        mockWorkspace = try WorkspaceDocument(for: directory, withContentsOf: directory, ofType: \"\")\n        searchState = mockWorkspace.searchState\n\n        // Add a few files\n        let folder1 = directory.appending(path: \"Folder 2\")\n        folder1File = CEWorkspaceFile(url: folder1)\n        let folder2 = directory.appending(path: \"Longer Folder With Some 💯 Special Chars ⁉️\")\n        folder2File = CEWorkspaceFile(url: folder2)\n        try FileManager.default.createDirectory(at: folder1, withIntermediateDirectories: true)\n        try FileManager.default.createDirectory(at: folder2, withIntermediateDirectories: true)\n\n        let fileURLs = [\n            directory.appending(path: \"File 1.txt\"),\n            folder1.appending(path: \"Documentation.docc\"),\n            folder2.appending(path: \"Makefile\")\n        ]\n\n        for index in 0..<fileURLs.count {\n            if index % 2 == 0 {\n                try String(\"Loren Ipsum\").write(to: fileURLs[index], atomically: true, encoding: .utf8)\n            } else {\n                try String(\"Aperiam asperiores\").write(to: fileURLs[index], atomically: true, encoding: .utf8)\n            }\n        }\n\n        files = fileURLs.map { CEWorkspaceFile(url: $0) }\n\n        files[1].parent = folder1File\n        files[2].parent = folder2File\n\n        mockWorkspace.searchState?.addProjectToIndex()\n\n        // NOTE: This is a temporary solution. In the future, a file watcher should track file updates\n        // and trigger an index update.\n        let startTime = Date()\n        let timeoutInSeconds = 2.0\n        while searchState.indexStatus != .done {\n            // Check every 0.1 seconds for index completion\n            try? await Task.sleep(nanoseconds: 100_000_000) // 0.1 seconds\n            if Date().timeIntervalSince(startTime) > timeoutInSeconds {\n                XCTFail(\"TIMEOUT: Indexing took to long or did not complete.\")\n                return\n            }\n        }\n\n        // Retrieve indexed documents from the indexer\n        guard let documentsInIndex = searchState.indexer?.documents() else {\n            XCTFail(\"No documents are in the index\")\n            return\n        }\n\n        // Verify that the setup function added the expected number of mock files to the index\n        XCTAssertEqual(documentsInIndex.count, 3)\n    }\n\n    // MARK: - Tear down\n    /// The mock directory along with the mock files will be removed\n    override func tearDown() async throws {\n        try? FileManager.default.removeItem(at: directory)\n    }\n\n    func reIndexWorkspace() async {\n        // IMPORTANT:\n        // This is only a temporary solution, in the feature a file watcher would track the file update\n        // and trigger a index update.\n        searchState.addProjectToIndex()\n        let startTime = Date()\n        while searchState.indexStatus != .done {\n            try? await Task.sleep(nanoseconds: 100_000_000)\n            if Date().timeIntervalSince(startTime) > 2.0 {\n                XCTFail(\"TIMEOUT: Indexing took to long or did not complete.\")\n                return\n            }\n        }\n    }\n\n    func testFindAndReplace() async {\n        let findAndReplaceExpectation = XCTestExpectation(description: \"Find and replace\")\n\n        Task {\n            do {\n                try await searchState.findAndReplace(query: \"Ipsum\", replacingTerm: \"muspi\")\n            } catch {\n                XCTFail(\"Find and replace failed: \\(error.localizedDescription)\")\n                return\n            }\n            findAndReplaceExpectation.fulfill()\n        }\n\n        await fulfillment(of: [findAndReplaceExpectation], timeout: 2)\n\n        await reIndexWorkspace()\n\n        let searchExpectation = XCTestExpectation(\n            description: \"Search for the new term that replaced 'Ipsum'('muspi').\"\n        )\n\n        Task {\n            await searchState.search(\"muspi\")\n            searchExpectation.fulfill()\n        }\n\n        await fulfillment(of: [searchExpectation], timeout: 2)\n        let searchResults = searchState.searchResult\n\n        // Expecting a result count of 0 due to the intentional use of a lowercase 'i'\n        XCTAssertEqual(searchResults.count, 2)\n    }\n\n    func testFindAndReplaceWithOptionContaining() async {\n        let findAndReplaceExpectation = XCTestExpectation(description: \"Find and replace\")\n\n        Task {\n            do {\n                try await searchState.findAndReplace(query: \"psu\", replacingTerm: \"OOO\")\n            } catch {\n                XCTFail(\"Find and replace failed: \\(error.localizedDescription)\")\n                return\n            }\n            findAndReplaceExpectation.fulfill()\n        }\n\n        await fulfillment(of: [findAndReplaceExpectation], timeout: 2)\n\n        await reIndexWorkspace()\n\n        let searchExpectation = XCTestExpectation(\n            description: \"Search for the new term that replaced 'Ipsum'('IOOOm').\"\n        )\n\n        Task {\n            await searchState.search(\"IOOOm\")\n            searchExpectation.fulfill()\n        }\n\n        await fulfillment(of: [searchExpectation], timeout: 2)\n        let searchResults = searchState.searchResult\n\n        XCTAssertEqual(searchResults.count, 2)\n    }\n\n    func testFindAndReplaceWithOptionMatchingWord() async {\n        let failedFindAndReplaceExpectation = XCTestExpectation(description: \"Failed Find and replace\")\n        let successfulFindAndReplaceExpectation = XCTestExpectation(description: \"Successful Find and replace\")\n\n        searchState.selectedMode[2] = .MatchingWord\n\n        Task {\n            do {\n                // this replacement should fail due to the .MatchingWord option\n                try await searchState.findAndReplace(query: \"psu\", replacingTerm: \"OOO\")\n            } catch {\n                XCTFail(\"Find and replace failed: \\(error.localizedDescription)\")\n                return\n            }\n            failedFindAndReplaceExpectation.fulfill()\n        }\n\n        await fulfillment(of: [failedFindAndReplaceExpectation], timeout: 2)\n\n        await reIndexWorkspace()\n\n        let searchExpectation = XCTestExpectation(description: \"Search for replaced word.\")\n\n        Task {\n            await searchState.search(\"IOOOm\")\n            searchExpectation.fulfill()\n        }\n\n        await fulfillment(of: [searchExpectation], timeout: 2)\n        let searchResults = searchState.searchResult\n\n        // Expecting a result count of 0 due to the intentional use of a incomplete word, while using .MatchingWord\n        XCTAssertEqual(searchResults.count, 0)\n\n        Task {\n            do {\n                // This should replace Ipsum correctly, because Ipsum is a whole word in 2 of the mock-documents\n                try await searchState.findAndReplace(query: \"Ipsum\", replacingTerm: \"OOO\")\n            } catch {\n                XCTFail(\"Find and replace failed: \\(error.localizedDescription)\")\n                return\n            }\n            successfulFindAndReplaceExpectation.fulfill()\n        }\n\n        await fulfillment(of: [successfulFindAndReplaceExpectation])\n\n        await reIndexWorkspace()\n\n        let searchExpectation2 = XCTestExpectation(\n            description: \"Search for the new term that replaced 'Ipsum'('OOO').\"\n        )\n\n        Task {\n            await searchState.search(\"OOO\")\n            searchExpectation2.fulfill()\n        }\n\n        await fulfillment(of: [searchExpectation2], timeout: 2)\n        let searchResults2 = searchState.searchResult\n\n        // 'Ipsum' got replaced by '000' so we expecting 2 results(000 appears in two documents)\n        XCTAssertEqual(searchResults2.count, 2)\n    }\n\n    func testFindAndReplaceWithOptionStartingWith() async {\n        let failedFindAndReplaceExpectation = XCTestExpectation(description: \"Failed Find and replace\")\n        let successfulFindAndReplaceExpectation = XCTestExpectation(description: \"Successful Find and replace\")\n\n        searchState.selectedMode[2] = .StartingWith\n\n        Task {\n            do {\n                // this replacement should fail due to the .StartingWith option\n                try await searchState.findAndReplace(query: \"psum\", replacingTerm: \"OOO\")\n            } catch {\n                XCTFail(\"Find and replace failed: \\(error.localizedDescription)\")\n                return\n            }\n            failedFindAndReplaceExpectation.fulfill()\n        }\n\n        await fulfillment(of: [failedFindAndReplaceExpectation], timeout: 2)\n\n        await reIndexWorkspace()\n\n        let searchExpectation = XCTestExpectation(description: \"Search for replaced word.\")\n\n        Task {\n            await searchState.search(\"OOO\")\n            searchExpectation.fulfill()\n        }\n\n        await fulfillment(of: [searchExpectation], timeout: 2)\n        let searchResults = searchState.searchResult\n\n        // Expecting a result count of 0 due to the intentional use of a incomplete word, while using .MatchingWord\n        XCTAssertEqual(searchResults.count, 0)\n\n        Task {\n            do {\n                // This should replace 'Ipsu' with '000' and result in '000m'\n                try await searchState.findAndReplace(query: \"Ipsu\", replacingTerm: \"OOO\")\n            } catch {\n                XCTFail(\"Find and replace failed: \\(error.localizedDescription)\")\n                return\n            }\n            successfulFindAndReplaceExpectation.fulfill()\n        }\n\n        await fulfillment(of: [successfulFindAndReplaceExpectation])\n\n        await reIndexWorkspace()\n\n        let searchExpectation2 = XCTestExpectation(\n            description: \"Search for the new term that replaced 'Ipsum'('OOO').\"\n        )\n\n        Task {\n            // Note that we are searching for '000m' instead of '000' to test that the whole word did not got replaced\n            await searchState.search(\"OOOm\")\n            searchExpectation2.fulfill()\n        }\n\n        await fulfillment(of: [searchExpectation2], timeout: 2)\n        let searchResults2 = searchState.searchResult\n\n        XCTAssertEqual(searchResults2.count, 2)\n    }\n\n    func testFindAndReplaceWithOptionEndingWith() async {\n        let failedFindAndReplaceExpectation = XCTestExpectation(description: \"Failed Find and replace\")\n        let successfulFindAndReplaceExpectation = XCTestExpectation(description: \"Successful Find and replace\")\n\n        searchState.selectedMode[2] = .EndingWith\n\n        Task {\n            do {\n                // this replacement should fail due to the .EndingWith option\n                try await searchState.findAndReplace(query: \"Ipsu\", replacingTerm: \"OOO\")\n            } catch {\n                XCTFail(\"Find and replace failed: \\(error.localizedDescription)\")\n                return\n            }\n            failedFindAndReplaceExpectation.fulfill()\n        }\n\n        await fulfillment(of: [failedFindAndReplaceExpectation], timeout: 2)\n\n        await reIndexWorkspace()\n\n        let searchExpectation = XCTestExpectation(description: \"Search for replaced word.\")\n\n        Task {\n            await searchState.search(\"OOO\")\n            searchExpectation.fulfill()\n        }\n\n        await fulfillment(of: [searchExpectation], timeout: 2)\n        let searchResults = searchState.searchResult\n\n        // Expecting a result count of 0 due to the intentional use of a incomplete word, while using .MatchingWord\n        XCTAssertEqual(searchResults.count, 0)\n\n        Task {\n            do {\n                // This should replace 'Ipsu' with '000' and result in '000m'\n                try await searchState.findAndReplace(query: \"sum\", replacingTerm: \"OOO\")\n            } catch {\n                XCTFail(\"Find and replace failed: \\(error.localizedDescription)\")\n                return\n            }\n            successfulFindAndReplaceExpectation.fulfill()\n        }\n\n        await fulfillment(of: [successfulFindAndReplaceExpectation])\n\n        await reIndexWorkspace()\n\n        let searchExpectation2 = XCTestExpectation(\n            description: \"Search for the new term that replaced 'Ipsum'('OOO').\"\n        )\n\n        Task {\n            // Test that the entire word 'Ipsum' is not replaced by searching for 'Ip000'.\n            await searchState.search(\"IpOOO\")\n            searchExpectation2.fulfill()\n        }\n\n        await fulfillment(of: [searchExpectation2], timeout: 2)\n        let searchResults2 = searchState.searchResult\n\n        XCTAssertEqual(searchResults2.count, 2)\n    }\n\n    // Not implemented\n    func testFindAndReplaceWithOptionRegularExpression() async { }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/Documents/WorkspaceDocument+SearchState+FindTests.swift",
    "content": "//\n//  WorkspaceDocument+SearchState+FindTests.swift\n//  CodeEditTests\n//\n//  Created by Tommy Ludwig on 26.01.24.\n//\n\nimport XCTest\n@testable import CodeEdit\n\nfinal class FindTests: XCTestCase {\n    private var directory: URL!\n    private var files: [CEWorkspaceFile] = []\n    private var mockWorkspace: WorkspaceDocument!\n    private var searchState: WorkspaceDocument.SearchState!\n\n    // MARK: - Setup\n    /// A mock WorkspaceDocument is created\n    /// 3 mock files are added to the index\n    /// which will be removed in the teardown function\n    override func setUp() async throws {\n        directory = try FileManager.default.url(\n            for: .developerApplicationDirectory,\n            in: .userDomainMask,\n            appropriateFor: nil,\n            create: true\n        )\n        .appending(path: \"CodeEdit\", directoryHint: .isDirectory)\n        .appending(path: \"WorkspaceClientTests\", directoryHint: .isDirectory)\n        try? FileManager.default.removeItem(at: directory)\n        try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)\n\n        mockWorkspace = try await WorkspaceDocument(for: directory, withContentsOf: directory, ofType: \"\")\n        searchState = await mockWorkspace.searchState\n\n        // Add a few files\n        let folder1 = directory.appending(path: \"Folder 2\")\n        let folder2 = directory.appending(path: \"Longer Folder With Some 💯 Special Chars ⁉️\")\n        try FileManager.default.createDirectory(at: folder1, withIntermediateDirectories: true)\n        try FileManager.default.createDirectory(at: folder2, withIntermediateDirectories: true)\n\n        let fileURLs = [\n            directory.appending(path: \"File 1.txt\"),\n            folder1.appending(path: \"Documentation.docc\"),\n            folder2.appending(path: \"Makefile\")\n        ]\n\n        for index in 0..<fileURLs.count {\n            if index % 2 == 0 {\n                try String(\"Loren Ipsum.\").write(to: fileURLs[index], atomically: true, encoding: .utf8)\n            } else {\n                try String(\"Aperiam*asperiores\").write(to: fileURLs[index], atomically: true, encoding: .utf8)\n            }\n        }\n\n        files = fileURLs.map { CEWorkspaceFile(url: $0) }\n\n        let parent1 = CEWorkspaceFile(url: folder1)\n        let parent2 = CEWorkspaceFile(url: folder2)\n        files[1].parent = parent1\n        files[2].parent = parent2\n\n        await mockWorkspace.searchState?.addProjectToIndex()\n\n        // The following code also tests whether the workspace is indexed correctly\n        // Wait until the index is up to date and flushed\n        let startTime = Date()\n        let timeoutInSeconds = 2.0\n        while searchState.indexStatus != .done {\n            // Check every 0.1 seconds for index completion\n            try? await Task.sleep(nanoseconds: 100_000_000) // 0.1 seconds\n            if Date().timeIntervalSince(startTime) > timeoutInSeconds {\n                XCTFail(\"TIMEOUT: Indexing took to long or did not complete.\")\n                return\n            }\n        }\n\n        // Retrieve indexed documents from the indexer\n        guard let documentsInIndex = searchState.indexer?.documents() else {\n            XCTFail(\"No documents are in the index\")\n            return\n        }\n\n        // Verify that the setup function added the expected number of mock files to the index\n        XCTAssertEqual(documentsInIndex.count, 3)\n    }\n\n    // MARK: - Tear down\n    /// The mock directory along with the mock files will be removed\n    override func tearDown() async throws {\n        try? FileManager.default.removeItem(at: directory)\n    }\n\n    func testGetSearchTerm() {\n        let query = \"test*/Quer@#y\"\n\n        searchState.selectedMode[2] = .Containing\n        XCTAssertEqual(searchState.getSearchTerm(query), \"*test*quer*y*\")\n\n        searchState.selectedMode[2] = .StartingWith\n        XCTAssertEqual(searchState.getSearchTerm(query), \"test*quer*y*\")\n\n        searchState.selectedMode[2] = .EndingWith\n        XCTAssertEqual(searchState.getSearchTerm(query), \"*test*quer*y\")\n\n        searchState.selectedMode[2] = .MatchingWord\n        XCTAssertEqual(searchState.getSearchTerm(query), \"test*quer*y\")\n\n        searchState.caseSensitive = true\n        XCTAssertEqual(searchState.getSearchTerm(query), \"test*Quer*y\")\n    }\n\n    func testStripSpecialCharacters() {\n        let string = \"test!@#Query\"\n        let strippedString = searchState.stripSpecialCharacters(from: string)\n        XCTAssertEqual(strippedString, \"test*Query\")\n    }\n\n    func testGetRegexPattern() {\n        let query = \"@(test. !*#Query\"\n\n        searchState.selectedMode[2] = .Containing\n        XCTAssertEqual(searchState.getRegexPattern(query), \"@\\\\(test\\\\. !\\\\*#Query\")\n\n        searchState.selectedMode[2] = .StartingWith\n        XCTAssertEqual(searchState.getRegexPattern(query), \"\\\\b@\\\\(test\\\\. !\\\\*#Query\")\n\n        searchState.selectedMode[2] = .EndingWith\n        XCTAssertEqual(searchState.getRegexPattern(query), \"@\\\\(test\\\\. !\\\\*#Query\\\\b\")\n\n        searchState.selectedMode[2] = .MatchingWord\n        XCTAssertEqual(searchState.getRegexPattern(query), \"\\\\b@\\\\(test\\\\. !\\\\*#Query\\\\b\")\n\n        // Enabling case sensitivity shouldn't affect the regex pattern because if case sensitivity is enabled,\n        // `NSRegularExpression.Options.caseInsensitive` is passed to `NSRegularExpression`.\n        searchState.caseSensitive = true\n        XCTAssertEqual(searchState.getRegexPattern(query), \"\\\\b@\\\\(test\\\\. !\\\\*#Query\\\\b\")\n    }\n\n    /// Tests the search functionality of the `WorkspaceDocument.SearchState` and `SearchIndexer`.\n    func testSearch() async {\n        await searchState.search(\"Ipsum\")\n        // Wait for the first search expectation to be fulfilled\n        await waitForExpectation {\n            searchState.searchResult.count == 2\n        } onTimeout: {\n            XCTFail(\"Search state did not find two results.\")\n        }\n\n        await searchState.search(\"asperiores\")\n        await waitForExpectation {\n            searchState.searchResult.count == 1\n        } onTimeout: {\n            XCTFail(\"Search state did not find correct results.\")\n        }\n    }\n\n    /// Checks if the search still returns proper results,\n    /// if the search term isn't a complete word\n    func testSearchWithOptionContaining() async {\n        await searchState.search(\"psu\")\n        await waitForExpectation {\n            searchState.searchResult.count == 2\n        } onTimeout: {\n            XCTFail(\"Search state did not find two results.\")\n        }\n\n        await searchState.search(\"erio\")\n        await waitForExpectation {\n            searchState.searchResult.count == 1\n        } onTimeout: {\n            XCTFail(\"Search state did not find correct results.\")\n        }\n    }\n\n    /// This test verifies the accuracy of the word search feature.\n    /// It first checks for the presence of 'Ipsum,' as done in previous tests.\n    /// Following that, it examines the occurrence of the fragment 'perior,'\n    /// which is not a complete word in any of the documents\n    func testSearchWithOptionMatchingWord() async {\n        // Set the search option to 'Matching Word'\n        searchState.selectedMode[2] = .MatchingWord\n\n        await searchState.search(\"Ipsum\")\n        await waitForExpectation {\n            searchState.searchResult.count == 2\n        } onTimeout: {\n            XCTFail(\"Search state did not find correct results.\")\n        }\n\n        // Check if incomplete words return no search results.\n        await searchState.search(\"perior\")\n        await waitForExpectation {\n            searchState.searchResult.isEmpty\n        } onTimeout: {\n            XCTFail(\"Search state did not find correct results.\")\n        }\n    }\n\n    func testSearchWithOptionStartingWith() async {\n        // Set the search option to 'Starting With'\n        searchState.selectedMode[2] = .StartingWith\n\n        await searchState.search(\"Ip\")\n        await waitForExpectation {\n            searchState.searchResult.count == 2\n        } onTimeout: {\n            XCTFail(\"Search state did not find two results.\")\n        }\n\n        await searchState.search(\"res\")\n        await waitForExpectation {\n            searchState.searchResult.isEmpty\n        } onTimeout: {\n            XCTFail(\"Search state did not find two results.\")\n        }\n    }\n\n    func testSearchWithOptionEndingWith() async {\n        // Set the search option to 'Ending with'\n        searchState.selectedMode[2] = .EndingWith\n\n        await searchState.search(\"um\")\n        await waitForExpectation {\n            searchState.searchResult.count == 2\n        } onTimeout: {\n            XCTFail(\"Search state did not find two results.\")\n        }\n\n        await searchState.search(\"asperi\")\n        await waitForExpectation {\n            searchState.searchResult.isEmpty\n        } onTimeout: {\n            XCTFail(\"Search state did not find correct results.\")\n        }\n    }\n\n    func testSearchWithOptionCaseSensitive() async {\n        searchState.caseSensitive = true\n        await searchState.search(\"ipsum\")\n        // Wait for the first search expectation to be fulfilled\n        await waitForExpectation {\n            // Expecting a result count of 0 due to the intentional use of a lowercase 'i'\n            searchState.searchResult.isEmpty\n        } onTimeout: {\n            XCTFail(\"Search state did not find correct results.\")\n        }\n\n        await searchState.search(\"Asperiores\")\n        await waitForExpectation {\n            // Anticipating zero results since the search is case-sensitive and we used an uppercase 'A'\n            searchState.searchResult.isEmpty\n        } onTimeout: {\n            XCTFail(\"Search state did not find correct results.\")\n        }\n    }\n\n    // Not implemented yet\n    func testSearchWithOptionRegularExpression() async { }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/Documents/WorkspaceDocument+SearchState+IndexTests.swift",
    "content": "//\n//  WorkspaceDocument+SearchState+IndexTests.swift\n//  CodeEditTests\n//\n//  Created by Tommy Ludwig on 26.01.24.\n//\n\nimport XCTest\n@testable import CodeEdit\n\nfinal class WorkspaceDocumentIndexTests: XCTestCase {\n    private var directory: URL!\n    private var files: [CEWorkspaceFile] = []\n    private var mockWorkspace: WorkspaceDocument!\n    private var searchState: WorkspaceDocument.SearchState!\n\n    private var folder1File: CEWorkspaceFile?\n    private var folder2File: CEWorkspaceFile?\n\n    // MARK: - Setup\n    /// A mock WorkspaceDocument is created\n    /// 3 mock files are added to the index\n    /// which will be removed in the teardown function\n    override func setUp() async throws {\n        directory = try FileManager.default.url(\n            for: .developerApplicationDirectory,\n            in: .userDomainMask,\n            appropriateFor: nil,\n            create: true\n        )\n        .appending(path: \"CodeEdit\", directoryHint: .isDirectory)\n        .appending(path: \"WorkspaceClientTests\", directoryHint: .isDirectory)\n        try? FileManager.default.removeItem(at: directory)\n        try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)\n\n        mockWorkspace = try await WorkspaceDocument(for: directory, withContentsOf: directory, ofType: \"\")\n        searchState = await mockWorkspace.searchState\n\n        // Add a few files\n        let folder1 = directory.appending(path: \"Folder 2\")\n        folder1File = CEWorkspaceFile(url: folder1)\n        let folder2 = directory.appending(path: \"Longer Folder With Some 💯 Special Chars ⁉️\")\n        folder2File = CEWorkspaceFile(url: folder2)\n        try FileManager.default.createDirectory(at: folder1, withIntermediateDirectories: true)\n        try FileManager.default.createDirectory(at: folder2, withIntermediateDirectories: true)\n\n        let fileURLs = [\n            directory.appending(path: \"File 1.txt\"),\n            folder1.appending(path: \"Documentation.docc\"),\n            folder2.appending(path: \"Makefile\")\n        ]\n\n        for index in 0..<fileURLs.count {\n            if index % 2 == 0 {\n                try String(\"Loren Ipsum\").write(to: fileURLs[index], atomically: true, encoding: .utf8)\n            } else {\n                try String(\"Aperiam asperiores\").write(to: fileURLs[index], atomically: true, encoding: .utf8)\n            }\n        }\n\n        files = fileURLs.map { CEWorkspaceFile(url: $0) }\n\n        files[1].parent = folder1File\n        files[2].parent = folder2File\n\n        await mockWorkspace.searchState?.addProjectToIndex()\n\n        // The following code also tests whether the workspace is indexed correctly\n        // Wait until the index is up to date and flushed\n        let startTime = Date()\n        let timeoutInSeconds = 2.0\n        while searchState.indexStatus != .done {\n            // Check every 0.1 seconds for index completion\n            try? await Task.sleep(nanoseconds: 100_000_000) // 0.1 seconds\n            if Date().timeIntervalSince(startTime) > timeoutInSeconds {\n                XCTFail(\"TIMEOUT: Indexing took to long or did not complete.\")\n                return\n            }\n        }\n\n        // Retrieve indexed documents from the indexer\n        guard let documentsInIndex = searchState.indexer?.documents() else {\n            XCTFail(\"No documents are in the index\")\n            return\n        }\n\n        // Verify that the setup function added the expected number of mock files to the index\n        XCTAssertEqual(documentsInIndex.count, 3)\n    }\n\n    // MARK: - Tear down\n    /// The mock directory along with the mock files will be removed\n    override func tearDown() async throws {\n        try? FileManager.default.removeItem(at: directory)\n    }\n\n    // Actually already tested in the setUp function, but for the sake of completeness\n    func testAddWorkspaceToIndex() {\n        // Retrieve indexed documents from the indexer\n        guard let documentsInIndex = searchState.indexer?.documents() else {\n            XCTFail(\"No documents are in the index\")\n            return\n        }\n\n        // Verify that the setup function added the expected number of mock files to the index\n        XCTAssertEqual(documentsInIndex.count, 3)\n    }\n\n    // The SearchState+Indexing file isn't complete yet\n    // So as it expands more tests will be added here\n}\n"
  },
  {
    "path": "CodeEditTests/Features/Editor/EditorStateRestorationTests.swift",
    "content": "//\n//  EditorStateRestorationTests.swift\n//  CodeEditTests\n//\n//  Created by Khan Winter on 7/3/25.\n//\n\nimport Testing\nimport Foundation\n@testable import CodeEdit\n\n@Suite\nstruct EditorStateRestorationTests {\n    @Test\n    func createsDatabase() throws {\n        try withTempDir { dir in\n            let url = dir.appending(path: \"database.db\")\n            _ = try EditorStateRestoration(url)\n            #expect(FileManager.default.fileExists(atPath: url.path(percentEncoded: false)))\n        }\n    }\n\n    @Test\n    func savesAndRetrievesStateForFile() throws {\n        try withTempDir { dir in\n            let url = dir.appending(path: \"database.db\")\n            let restoration = try EditorStateRestoration(url)\n\n            // Update some state\n            restoration.updateRestorationState(\n                for: dir.appending(path: \"file.txt\"),\n                data: .init(cursorPositions: [], scrollPosition: .zero)\n            )\n\n            // Retrieve it\n            #expect(\n                restoration.restorationState(for: dir.appending(path: \"file.txt\"))\n                == EditorStateRestoration.StateRestorationData(cursorPositions: [], scrollPosition: .zero)\n            )\n        }\n    }\n\n    @Test\n    func savesScrollPosition() throws {\n        try withTempDir { dir in\n            let url = dir.appending(path: \"database.db\")\n            let restoration = try EditorStateRestoration(url)\n\n            // Update some state\n            restoration.updateRestorationState(\n                for: dir.appending(path: \"file.txt\"),\n                data: .init(cursorPositions: [], scrollPosition: CGPoint(x: 100, y: 100))\n            )\n\n            // Retrieve it\n            #expect(\n                restoration.restorationState(for: dir.appending(path: \"file.txt\"))\n                == EditorStateRestoration.StateRestorationData(\n                    cursorPositions: [],\n                    scrollPosition: CGPoint(x: 100, y: 100)\n                )\n            )\n        }\n    }\n\n    @Test\n    func clearsCorruptedDatabase() throws {\n        try withTempDir { dir in\n            let url = dir.appending(path: \"database.db\")\n            try \"bad data\".write(to: url, atomically: true, encoding: .utf8)\n            // This will throw if it can't connect to the database.\n            _ = try EditorStateRestoration(url)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/Editor/UndoManagerRegistrationTests.swift",
    "content": "//\n//  UndoManagerRegistrationTests.swift\n//  CodeEditTests\n//\n//  Created by Khan Winter on 7/3/25.\n//\n\n@testable import CodeEdit\nimport Testing\nimport Foundation\nimport CodeEditTextView\n\n@MainActor\n@Suite\nstruct UndoManagerRegistrationTests {\n    let registrar = UndoManagerRegistration()\n    let file = CEWorkspaceFile(url: URL(filePath: \"/fake/dir/file.txt\"))\n    let textView = TextView(string: \"hello world\")\n\n    @Test\n    func newUndoManager() {\n        let manager = registrar.manager(forFile: file)\n        #expect(manager.canUndo == false)\n    }\n\n    @Test\n    func undoManagersRetained() throws {\n        let manager = registrar.manager(forFile: file)\n        textView.setUndoManager(manager)\n        manager.registerMutation(.init(insert: \"hello\", at: 0, limit: 11))\n\n        let sameManager = registrar.manager(forFile: file)\n        #expect(manager === sameManager)\n        #expect(sameManager.canUndo)\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/LSP/BufferingServerConnection.swift",
    "content": "//\n//  BufferingServerConnection.swift\n//  CodeEditTests\n//\n//  Created by Khan Winter on 9/10/24.\n//\n\nimport Foundation\nimport LanguageClient\nimport LanguageServerProtocol\nimport JSONRPC\n\n/// Mock server connection that retains all requests and notifications in an array for comparing later.\n///\n/// To listen for changes, this type produces an async stream of all requests and notifications. Use the\n/// `clientEventSequence` sequence to receive a copy of both whenever they're updated.\n/// \nclass BufferingServerConnection: ServerConnection {\n    typealias ClientEventSequence = AsyncStream<([ClientRequest], [ClientNotification])>\n\n    public var eventSequence: EventSequence\n\n    /// A sequence of all events.\n    public var clientEventSequence: ClientEventSequence\n    private var clientEventContinuation: ClientEventSequence.Continuation\n\n    private var id = 0\n\n    public var clientRequests: [ClientRequest] = []\n    public var clientNotifications: [ClientNotification] = []\n\n    init() {\n        let (sequence, _) = EventSequence.makeStream()\n        self.eventSequence = sequence\n        (clientEventSequence, clientEventContinuation) = ClientEventSequence.makeStream()\n    }\n\n    func sendNotification(_ notif: ClientNotification) async throws {\n        clientNotifications.append(notif)\n        clientEventContinuation.yield((clientRequests, clientNotifications))\n    }\n\n    func sendRequest<Response: Decodable & Sendable>(_ request: ClientRequest) async throws -> Response {\n        defer {\n            clientEventContinuation.yield((clientRequests, clientNotifications))\n        }\n\n        clientRequests.append(request)\n        id += 1\n        let response: Codable\n        switch request {\n        case .initialize:\n            var capabilities = ServerCapabilities()\n            capabilities.textDocumentSync = .optionA(.init(\n                openClose: true, change: .incremental, willSave: true, willSaveWaitUntil: false, save: .optionA(true)\n            ))\n            response = InitializationResponse(capabilities: .init(), serverInfo: nil)\n        default:\n            response = JSONRPCResponse(id: .numericId(0), result: JSONRPCErrors.internalError)\n        }\n        let data = try JSONEncoder().encode(response)\n        return try JSONDecoder().decode(Response.self, from: data)\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/LSP/LanguageServer+CodeFileDocument.swift",
    "content": "//\n//  LanguageServer+CodeFileDocument.swift\n//  CodeEditTests\n//\n//  Created by Khan Winter on 9/9/24.\n//\n\nimport XCTest\nimport CodeEditTextView\nimport CodeEditSourceEditor\nimport LanguageClient\nimport LanguageServerProtocol\n\n@testable import CodeEdit\n\n/// This is an integration test for notifications relating to the ``CodeFileDocument`` class.\n/// \n/// For *unit* tests with the language server class, add tests to the `LanguageServer+DocumentObjects` test class as\n/// it's cleaner and makes correct use of the mock document type.\nfinal class LanguageServerCodeFileDocumentTests: XCTestCase {\n    // Test opening documents in CodeEdit triggers creating a language server,\n    // further opened documents don't create new servers\n\n    typealias LanguageServerType = LanguageServer<CodeFileDocument>\n\n    var tempTestDir: URL!\n\n    override func setUp() {\n        continueAfterFailure = false\n        do {\n            let tempDir = FileManager.default.temporaryDirectory.appending(\n                path: \"codeedit-lsp-tests\"\n            )\n            // Clean up first.\n            if FileManager.default.fileExists(atPath: tempDir.absoluteURL.path()) {\n                try FileManager.default.removeItem(at: tempDir)\n            }\n            try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n            tempTestDir = tempDir\n        } catch {\n            XCTFail(error.localizedDescription)\n        }\n    }\n\n    override func tearDown() {\n        do {\n            try FileManager.default.removeItem(at: tempTestDir)\n        } catch {\n            XCTFail(error.localizedDescription)\n        }\n    }\n\n    func makeTestServer() async throws -> (connection: BufferingServerConnection, server: LanguageServerType) {\n        let bufferingConnection = BufferingServerConnection()\n        var capabilities = ServerCapabilities()\n        capabilities.textDocumentSync = .optionA(\n            TextDocumentSyncOptions(\n                openClose: true,\n                change: .incremental,\n                willSave: true,\n                willSaveWaitUntil: false,\n                save: nil\n            )\n        )\n        let server = LanguageServerType(\n            languageId: .swift,\n            binary: .init(execPath: \"\", args: [], env: nil),\n            lspInstance: InitializingServer(\n                server: bufferingConnection,\n                initializeParamsProvider: LanguageServerType.getInitParams(workspacePath: tempTestDir.path())\n            ),\n            lspPid: -1,\n            serverCapabilities: capabilities,\n            rootPath: tempTestDir,\n            logContainer: LanguageServerLogContainer(language: .swift)\n        )\n        _ = try await server.lspInstance.initializeIfNeeded()\n        return (connection: bufferingConnection, server: server)\n    }\n\n    func makeTestWorkspace() throws -> (WorkspaceDocument, CEWorkspaceFileManager) {\n        let workspace = WorkspaceDocument()\n        try workspace.read(from: tempTestDir, ofType: \"\")\n        guard let fileManager = workspace.workspaceFileManager else {\n            XCTFail(\"No File Manager\")\n            fatalError(\"No File Manager\") // never runs\n        }\n        return (workspace, fileManager)\n    }\n\n    func openCodeFile(\n        for server: LanguageServerType,\n        connection: BufferingServerConnection,\n        file: CEWorkspaceFile,\n        syncOption: TwoTypeOption<TextDocumentSyncOptions, TextDocumentSyncKind>?\n    ) async throws -> CodeFileDocument {\n        let codeFile = try await CodeFileDocument(\n            for: file.url,\n            withContentsOf: file.url,\n            ofType: \"public.swift-source\"\n        )\n\n        // This is usually sent from the LSPService\n        try await server.openDocument(codeFile)\n\n        await waitForClientState(\n            (\n                [.initialize],\n                [.initialized, .textDocumentDidOpen]\n            ),\n            connection: connection,\n            description: \"Initialized (2) and opened (1) notification count\"\n        )\n\n        // Set up full content changes\n        server.serverCapabilities = ServerCapabilities()\n        server.serverCapabilities.textDocumentSync = syncOption\n\n        return codeFile\n    }\n\n    func waitForClientState(\n        _ expectedValue: ([ClientRequest.Method], [ClientNotification.Method]),\n        connection: BufferingServerConnection,\n        description: String\n    ) async {\n        let expectation = expectation(description: description)\n\n        await withTaskGroup(of: Void.self) { group in\n            group.addTask { await self.fulfillment(of: [expectation], timeout: 2) }\n            group.addTask {\n                for await events in connection.clientEventSequence\n                where events.0.map(\\.method) == expectedValue.0 && events.1.map(\\.method) == expectedValue.1 {\n                    expectation.fulfill()\n                    return\n                }\n            }\n        }\n    }\n\n    // MARK: - Open Close\n\n    @MainActor\n    func testOpenCloseFileNotifications() async throws {\n        // Set up test server\n        let (connection, server) = try await makeTestServer()\n\n        // This service should receive the didOpen/didClose notifications\n        let lspService = ServiceContainer.resolve(.singleton, LSPService.self)\n        await MainActor.run { lspService?.languageClients[.init(.swift, tempTestDir.path() + \"/\")] = server }\n\n        // Set up workspace\n        let (workspace, fileManager) = try makeTestWorkspace()\n        CodeEditDocumentController.shared.addDocument(workspace)\n\n        // Add a CEWorkspaceFile\n        _ = try fileManager.addFile(fileName: \"example\", toFile: fileManager.workspaceItem, useExtension: \"swift\")\n        guard let file = fileManager.childrenOfFile(fileManager.workspaceItem)?.first else {\n            XCTFail(\"No File\")\n            return\n        }\n\n        // Create a CodeFileDocument to test with, attach it to the workspace and file\n        let codeFile = try CodeFileDocument(\n            for: file.url,\n            withContentsOf: file.url,\n            ofType: \"public.swift-source\"\n        )\n        file.fileDocument = codeFile\n        CodeEditDocumentController.shared.addDocument(codeFile)\n\n        await waitForClientState(\n            (\n                [.initialize],\n                [.initialized, .textDocumentDidOpen]\n            ),\n            connection: connection,\n            description: \"Pre-close event count\"\n        )\n\n        // This should then trigger a documentDidClose event\n        codeFile.close()\n\n        await waitForClientState(\n            (\n                [.initialize],\n                [.initialized, .textDocumentDidOpen, .textDocumentDidClose]\n            ),\n            connection: connection,\n            description: \"Post-close event count\"\n        )\n    }\n\n    // MARK: - Test Document Edit\n\n    /// Assert the changed contents received by the buffered connection\n    func assertExpectedContentChanges(connection: BufferingServerConnection, changes: [String]) {\n        var foundChangeContents: [String] = []\n\n        for notification in connection.clientNotifications {\n            switch notification {\n            case let .textDocumentDidChange(params):\n                foundChangeContents.append(contentsOf: params.contentChanges.map(\\.text))\n            default:\n                continue\n            }\n        }\n\n        XCTAssertEqual(changes, foundChangeContents)\n    }\n\n    @MainActor\n    func testDocumentEditNotificationsFullChanges() async throws {\n        // Set up a workspace in the temp directory\n        let (_, fileManager) = try makeTestWorkspace()\n\n        // Make our example file\n        _ = try fileManager.addFile(fileName: \"example\", toFile: fileManager.workspaceItem, useExtension: \"swift\")\n        guard let file = fileManager.childrenOfFile(fileManager.workspaceItem)?.first else {\n            XCTFail(\"No File\")\n            return\n        }\n\n        // Need to test both definitions for server capabilities\n        let syncOptions: [TwoTypeOption<TextDocumentSyncOptions, TextDocumentSyncKind>] = [\n            .optionA(.init(change: .full)),\n            .optionB(.full)\n        ]\n\n        for option in syncOptions {\n            // Set up test server\n            let (connection, server) = try await makeTestServer()\n            // Create a CodeFileDocument to test with, attach it to the workspace and file\n            let codeFile = try await openCodeFile(for: server, connection: connection, file: file, syncOption: option)\n            XCTAssertNotNil(codeFile.languageServerObjects.textCoordinator.languageServer)\n            codeFile.languageServerObjects.textCoordinator.setUpUpdatesTask()\n            codeFile.content?.replaceString(in: .zero, with: #\"func testFunction() -> String { \"Hello \" }\"#)\n\n            let textView = TextView(string: \"\")\n            textView.setTextStorage(codeFile.content!)\n            textView.delegate = codeFile.languageServerObjects.textCoordinator\n\n            textView.replaceCharacters(in: NSRange(location: 39, length: 0), with: \"Worlld\")\n            textView.replaceCharacters(in: NSRange(location: 39, length: 6), with: \"\")\n            textView.replaceCharacters(in: NSRange(location: 39, length: 0), with: \"World\")\n\n            // Added one notification\n            await waitForClientState(\n                (\n                    [.initialize],\n                    [.initialized, .textDocumentDidOpen, .textDocumentDidChange]\n                ),\n                connection: connection,\n                description: \"Edited notification count\"\n            )\n\n            // Make sure our text view is intact\n            XCTAssertEqual(textView.string, #\"func testFunction() -> String { \"Hello World\" }\"#)\n\n            // Expect only one change due to throttling.\n            assertExpectedContentChanges(\n                connection: connection,\n                changes: [#\"func testFunction() -> String { \"Hello World\" }\"#]\n            )\n        }\n    }\n\n    @MainActor\n    func testDocumentEditNotificationsIncrementalChanges() async throws {\n        // Set up test server\n        let (_, _) = try await makeTestServer()\n\n        // Set up a workspace in the temp directory\n        let (_, fileManager) = try makeTestWorkspace()\n\n        // Make our example file\n        _ = try fileManager.addFile(fileName: \"example\", toFile: fileManager.workspaceItem, useExtension: \"swift\")\n        guard let file = fileManager.childrenOfFile(fileManager.workspaceItem)?.first else {\n            XCTFail(\"No File\")\n            return\n        }\n\n        let syncOptions: [TwoTypeOption<TextDocumentSyncOptions, TextDocumentSyncKind>] = [\n            .optionA(.init(change: .incremental)),\n            .optionB(.incremental)\n        ]\n\n        for option in syncOptions {\n            // Set up test server\n            let (connection, server) = try await makeTestServer()\n            let codeFile = try await openCodeFile(for: server, connection: connection, file: file, syncOption: option)\n\n            XCTAssertNotNil(codeFile.languageServerObjects.textCoordinator.languageServer)\n            codeFile.languageServerObjects.textCoordinator.setUpUpdatesTask()\n            codeFile.content?.replaceString(in: .zero, with: #\"func testFunction() -> String { \"Hello \" }\"#)\n\n            let textView = TextView(string: \"\")\n            textView.setTextStorage(codeFile.content!)\n            textView.delegate =  codeFile.languageServerObjects.textCoordinator\n            textView.replaceCharacters(in: NSRange(location: 39, length: 0), with: \"Worlld\")\n            textView.replaceCharacters(in: NSRange(location: 39, length: 6), with: \"\")\n            textView.replaceCharacters(in: NSRange(location: 39, length: 0), with: \"World\")\n\n            // Throttling means we should receive one edited notification + init notification + didOpen + init request\n            await waitForClientState(\n                (\n                    [.initialize],\n                    [.initialized, .textDocumentDidOpen, .textDocumentDidChange]\n                ),\n                connection: connection,\n                description: \"Edited notification count\"\n            )\n\n            // Make sure our text view is intact\n            XCTAssertEqual(textView.string, #\"func testFunction() -> String { \"Hello World\" }\"#)\n\n            // Expect three content changes.\n            assertExpectedContentChanges(\n                connection: connection,\n                changes: [\"Worlld\", \"\", \"World\"]\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/LSP/LanguageServer+DocumentObjects.swift",
    "content": "//\n//  LanguageServer+DocumentObjects.swift\n//  CodeEditTests\n//\n//  Created by Khan Winter on 2/12/25.\n//\n\nimport XCTest\nimport CodeEditTextView\nimport CodeEditSourceEditor\nimport CodeEditLanguages\nimport LanguageClient\nimport LanguageServerProtocol\n\n@testable import CodeEdit\n\nfinal class LanguageServerDocumentObjectsTests: XCTestCase {\n    final class MockDocumentType: LanguageServerDocument {\n        var content: NSTextStorage?\n        var languageServerURI: String?\n        var languageServerObjects: LanguageServerDocumentObjects<MockDocumentType>\n\n        init() {\n            self.content = NSTextStorage(string: \"hello world\")\n            self.languageServerURI = \"/test/file/path\"\n            self.languageServerObjects = .init()\n        }\n\n        func getLanguage() -> CodeLanguage {\n            .swift\n        }\n    }\n\n    typealias LanguageServerType = LanguageServer<MockDocumentType>\n\n    var document: MockDocumentType!\n    var server: LanguageServerType!\n\n    // MARK: - Set Up\n\n    override func setUp() async throws {\n        var capabilities = ServerCapabilities()\n        capabilities.textDocumentSync = .optionA(.init(openClose: true, change: .full))\n        capabilities.semanticTokensProvider = .optionA(.init(legend: .init(tokenTypes: [], tokenModifiers: [])))\n        server = LanguageServerType(\n            languageId: .swift,\n            binary: .init(execPath: \"\", args: [], env: nil),\n            lspInstance: InitializingServer(\n                server: BufferingServerConnection(),\n                initializeParamsProvider: LanguageServerType.getInitParams(workspacePath: \"/\")\n            ),\n            lspPid: -1,\n            serverCapabilities: capabilities,\n            rootPath: URL(fileURLWithPath: \"\"),\n            logContainer: LanguageServerLogContainer(language: .swift)\n        )\n        _ = try await server.lspInstance.initializeIfNeeded()\n        document = MockDocumentType()\n    }\n\n    // MARK: - Tests\n\n    func testOpenDocumentRegistersObjects() async throws {\n        try await server.openDocument(document)\n        XCTAssertNotNil(document.languageServerObjects.highlightProvider)\n        XCTAssertNotNil(document.languageServerObjects.textCoordinator)\n        XCTAssertNotNil(server.openFiles.document(for: document.languageServerURI ?? \"\"))\n    }\n\n    func testCloseDocumentClearsObjects() async throws {\n        guard let languageServerURI = document.languageServerURI else {\n            XCTFail(\"Language server URI missing on a mock object\")\n            return\n        }\n        try await server.openDocument(document)\n        XCTAssertNotNil(server.openFiles.document(for: languageServerURI))\n\n        try await server.closeDocument(languageServerURI)\n        XCTAssertNil(document.languageServerObjects.highlightProvider.languageServer)\n        XCTAssertNil(document.languageServerObjects.textCoordinator.languageServer)\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/LSP/Registry.swift",
    "content": "//\n//  Registry.swift\n//  CodeEdit\n//\n//  Created by Abe Malla on 2/2/25.\n//\n\nimport Testing\nimport Foundation\n@testable import CodeEdit\n\n@MainActor\n@Suite()\nstruct RegistryTests {\n    var registry: RegistryManager = RegistryManager()\n\n    // MARK: - Download Tests\n\n    @Test\n    func registryDownload() async throws {\n        await registry.downloadRegistryItems()\n\n        #expect(registry.downloadError == nil)\n\n        let registryJsonPath = registry.installPath.appending(path: \"registry.json\")\n        let checksumPath = registry.installPath.appending(path: \"checksums.txt\")\n\n        #expect(FileManager.default.fileExists(atPath: registryJsonPath.path), \"Registry JSON file should exist.\")\n        #expect(FileManager.default.fileExists(atPath: checksumPath.path), \"Checksum file should exist.\")\n    }\n\n    // MARK: - Decoding Tests\n\n    @Test\n    func registryDecoding() async throws {\n        await registry.downloadRegistryItems()\n\n        let registryJsonPath = registry.installPath.appending(path: \"registry.json\")\n        let jsonData = try Data(contentsOf: registryJsonPath)\n\n        let decoder = JSONDecoder()\n        decoder.keyDecodingStrategy = .convertFromSnakeCase\n        let entries = try decoder.decode([RegistryItem].self, from: jsonData)\n\n        #expect(entries.isEmpty == false, \"Registry should not be empty after decoding.\")\n\n        if let actionlint = entries.first(where: { $0.name == \"actionlint\" }) {\n            #expect(actionlint.description == \"Static checker for GitHub Actions workflow files.\")\n            #expect(actionlint.licenses == [\"MIT\"])\n            #expect(actionlint.languages == [\"YAML\"])\n            #expect(actionlint.categories == [\"Linter\"])\n        } else {\n            Issue.record(\"Could not find actionlint in registry\")\n        }\n    }\n\n    @Test\n    func handlesVersionOverrides() async throws {\n        await registry.downloadRegistryItems()\n\n        let registryJsonPath = registry.installPath.appending(path: \"registry.json\")\n        let jsonData = try Data(contentsOf: registryJsonPath)\n\n        let decoder = JSONDecoder()\n        decoder.keyDecodingStrategy = .convertFromSnakeCase\n        let entries = try decoder.decode([RegistryItem].self, from: jsonData)\n\n        if let adaServer = entries.first(where: { $0.name == \"ada-language-server\" }) {\n            #expect(adaServer.source.versionOverrides != nil, \"Version overrides should be present.\")\n            #expect(adaServer.source.versionOverrides!.isEmpty == false, \"Version overrides should not be empty.\")\n        } else {\n            Issue.record(\"Could not find ada-language-server to test version overrides\")\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/LSP/SemanticTokens/SemanticTokenMapTests.swift",
    "content": "//\n//  SemanticTokenMapTests.swift\n//  CodeEditTests\n//\n//  Created by Khan Winter on 12/14/24.\n//\n\nimport XCTest\nimport CodeEditSourceEditor\nimport LanguageServerProtocol\n@testable import CodeEdit\n\nfinal class SemanticTokenMapTests: XCTestCase {\n    // Ignores the line parameter and just returns a range from the char and length for testing\n    struct MockRangeProvider: SemanticTokenMapRangeProvider {\n        func nsRangeFrom(line: UInt32, char: UInt32, length: UInt32) -> NSRange? {\n            return NSRange(location: Int(char), length: Int(length))\n        }\n    }\n\n    let testLegend: SemanticTokensLegend = .init(\n        tokenTypes: [\n            \"include\",\n            \"constructor\",\n            \"keyword\",\n            \"boolean\",\n            \"comment\",\n            \"number\"\n        ],\n        tokenModifiers: [\n            \"declaration\",\n            \"definition\",\n            \"readonly\",\n            \"async\",\n            \"modification\",\n            \"defaultLibrary\"\n        ]\n    )\n    var mockProvider: MockRangeProvider!\n\n    override func setUp() async throws {\n        mockProvider = await MockRangeProvider()\n    }\n\n    @MainActor\n    func testOptionA() {\n        let map = SemanticTokenMap(semanticCapability: .optionA(SemanticTokensOptions(legend: testLegend)))\n\n        // Test decode modifiers\n        let modifierRaw = UInt32(0b1101)\n        let decodedModifiers = map.decodeModifier(modifierRaw)\n        XCTAssertEqual([.declaration, .readonly, .async], decodedModifiers)\n\n        // Test decode tokens\n        let tokens = SemanticTokens(tokens: [\n            SemanticToken(line: 0, char: 0, length: 1, type: 1000000, modifiers: 0b11), // First two indices set\n            SemanticToken(line: 0, char: 1, length: 2, type: 0, modifiers: 0b100100), // 6th and 3rd indices set\n            SemanticToken(line: 0, char: 4, length: 1, type: 1, modifiers: 0b101),\n            SemanticToken(line: 0, char: 5, length: 1, type: 4, modifiers: 0b1010),\n            SemanticToken(line: 0, char: 7, length: 10, type: 0, modifiers: 0)\n        ])\n        let decoded = map.decode(tokens: tokens, using: mockProvider)\n        XCTAssertEqual(decoded.count, 5, \"Decoded count\")\n\n        XCTAssertEqual(decoded[0].range, NSRange(location: 0, length: 1), \"Decoded range\")\n        XCTAssertEqual(decoded[1].range, NSRange(location: 1, length: 2), \"Decoded range\")\n        XCTAssertEqual(decoded[2].range, NSRange(location: 4, length: 1), \"Decoded range\")\n        XCTAssertEqual(decoded[3].range, NSRange(location: 5, length: 1), \"Decoded range\")\n        XCTAssertEqual(decoded[4].range, NSRange(location: 7, length: 10), \"Decoded range\")\n\n        XCTAssertEqual(decoded[0].capture, nil, \"No Decoded Capture\")\n        XCTAssertEqual(decoded[1].capture, .include, \"No Decoded Capture\")\n        XCTAssertEqual(decoded[2].capture, .constructor, \"Decoded Capture\")\n        XCTAssertEqual(decoded[3].capture, .comment, \"Decoded Capture\")\n        XCTAssertEqual(decoded[4].capture, .include, \"No Decoded Capture\")\n\n        XCTAssertEqual(decoded[0].modifiers, [.declaration, .definition], \"Decoded Modifiers\")\n        XCTAssertEqual(decoded[1].modifiers, [.readonly, .defaultLibrary], \"Decoded Modifiers\")\n        XCTAssertEqual(decoded[2].modifiers, [.declaration, .readonly], \"Decoded Modifiers\")\n        XCTAssertEqual(decoded[3].modifiers, [.definition, .async], \"Decoded Modifiers\")\n        XCTAssertEqual(decoded[4].modifiers, [], \"Decoded Modifiers\")\n    }\n\n    @MainActor\n    func testOptionB() {\n        let map = SemanticTokenMap(semanticCapability: .optionB(SemanticTokensRegistrationOptions(legend: testLegend)))\n\n        // Test decode modifiers\n        let modifierRaw = UInt32(0b1101)\n        let decodedModifiers = map.decodeModifier(modifierRaw)\n        XCTAssertEqual([.declaration, .readonly, .async], decodedModifiers)\n\n        // Test decode tokens\n        let tokens = SemanticTokens(tokens: [\n            SemanticToken(line: 0, char: 0, length: 1, type: 100, modifiers: 0b11),     // First two indices set\n            SemanticToken(line: 0, char: 1, length: 2, type: 0, modifiers: 0b100100), // 6th and 3rd indices set\n            SemanticToken(line: 0, char: 4, length: 1, type: 1, modifiers: 0b101),\n            SemanticToken(line: 0, char: 5, length: 1, type: 4, modifiers: 0b1010),\n            SemanticToken(line: 0, char: 7, length: 10, type: 0, modifiers: 0)\n        ])\n        let decoded = map.decode(tokens: tokens, using: mockProvider)\n        XCTAssertEqual(decoded.count, 5, \"Decoded count\")\n\n        XCTAssertEqual(decoded[0].range, NSRange(location: 0, length: 1), \"Decoded range\")\n        XCTAssertEqual(decoded[1].range, NSRange(location: 1, length: 2), \"Decoded range\")\n        XCTAssertEqual(decoded[2].range, NSRange(location: 4, length: 1), \"Decoded range\")\n        XCTAssertEqual(decoded[3].range, NSRange(location: 5, length: 1), \"Decoded range\")\n        XCTAssertEqual(decoded[4].range, NSRange(location: 7, length: 10), \"Decoded range\")\n\n        XCTAssertEqual(decoded[0].capture, nil, \"No Decoded Capture\")\n        XCTAssertEqual(decoded[1].capture, .include, \"No Decoded Capture\")\n        XCTAssertEqual(decoded[2].capture, .constructor, \"Decoded Capture\")\n        XCTAssertEqual(decoded[3].capture, .comment, \"Decoded Capture\")\n        XCTAssertEqual(decoded[4].capture, .include, \"No Decoded Capture\")\n\n        XCTAssertEqual(decoded[0].modifiers, [.declaration, .definition], \"Decoded Modifiers\")\n        XCTAssertEqual(decoded[1].modifiers, [.readonly, .defaultLibrary], \"Decoded Modifiers\")\n        XCTAssertEqual(decoded[2].modifiers, [.declaration, .readonly], \"Decoded Modifiers\")\n        XCTAssertEqual(decoded[3].modifiers, [.definition, .async], \"Decoded Modifiers\")\n        XCTAssertEqual(decoded[4].modifiers, [], \"Decoded Modifiers\")\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/LSP/SemanticTokens/SemanticTokenStorageTests.swift",
    "content": "//\n//  SemanticTokenStorageTests.swift\n//  CodeEdit\n//\n//  Created by Khan Winter on 12/26/24.\n//\n\nimport Foundation\nimport Testing\nimport CodeEditSourceEditor\nimport LanguageServerProtocol\n@testable import CodeEdit\n\n// For easier comparison while setting semantic tokens\nextension SemanticToken: @retroactive Equatable {\n    public static func == (lhs: SemanticToken, rhs: SemanticToken) -> Bool {\n        lhs.type == rhs.type\n        && lhs.modifiers == rhs.modifiers\n        && lhs.line == rhs.line\n        && lhs.char == rhs.char\n        && lhs.length == rhs.length\n    }\n}\n\n@Suite\nstruct SemanticTokenStorageTests {\n    let storage = SemanticTokenStorage()\n\n    let semanticTokens = [\n        SemanticToken(line: 0, char: 0, length: 10, type: 0, modifiers: 0),\n        SemanticToken(line: 1, char: 2, length: 5, type: 2, modifiers: 3),\n        SemanticToken(line: 3, char: 8, length: 10, type: 1, modifiers: 0)\n    ]\n\n    @Test\n    func initialState() async throws {\n        #expect(storage.state == nil)\n        #expect(storage.hasReceivedData == false)\n        #expect(storage.lastResultId == nil)\n    }\n\n    @Test\n    func setData() async throws {\n        storage.setData(\n            SemanticTokens(\n                resultId: \"1234\",\n                tokens: semanticTokens\n            )\n        )\n\n        let state = try #require(storage.state)\n        #expect(state.tokens == semanticTokens)\n        #expect(state.resultId == \"1234\")\n\n        #expect(storage.lastResultId == \"1234\")\n        #expect(storage.hasReceivedData == true)\n    }\n\n    @Test\n    func overwriteDataRepeatedly() async throws {\n        let dataToApply: [(String?, [SemanticToken])] = [\n            (nil, semanticTokens),\n            (\"1\", []),\n            (\"2\", semanticTokens.dropLast()),\n            (\"3\", semanticTokens)\n        ]\n        for (resultId, tokens) in dataToApply {\n            storage.setData(SemanticTokens(resultId: resultId, tokens: tokens))\n            let state = try #require(storage.state)\n            #expect(state.tokens == tokens)\n            #expect(state.resultId == resultId)\n            #expect(storage.lastResultId == resultId)\n            #expect(storage.hasReceivedData == true)\n        }\n    }\n\n    @Suite(\"ApplyDeltas\")\n    struct TokensDeltasTests {\n        struct DeltaEdit {\n            let start: Int\n            let deleteCount: Int\n            let data: [Int]\n\n            func makeString() -> String {\n                let dataString = data.map { String($0) }.joined(separator: \",\")\n                return \"{\\\"start\\\": \\(start), \\\"deleteCount\\\": \\(deleteCount), \\\"data\\\": [\\(dataString)] }\"\n            }\n        }\n\n        func makeDelta(resultId: String, edits: [DeltaEdit]) throws -> SemanticTokensDelta {\n            // This is unfortunate, but there's no public initializer for these structs.\n            // So we have to decode them from JSON strings\n            let editsString = edits.map { $0.makeString() }.joined(separator: \",\")\n            let deltasJSON = \"{ \\\"resultId\\\": \\\"\\(resultId)\\\", \\\"edits\\\": [\\(editsString)] }\"\n            let decoder = JSONDecoder()\n            let deltas = try decoder.decode(SemanticTokensDelta.self, from: Data(deltasJSON.utf8))\n            return deltas\n        }\n\n        let storage: SemanticTokenStorage\n\n        let semanticTokens = [\n            SemanticToken(line: 0, char: 0, length: 10, type: 0, modifiers: 0),\n            SemanticToken(line: 1, char: 2, length: 5, type: 2, modifiers: 3),\n            SemanticToken(line: 3, char: 8, length: 10, type: 1, modifiers: 0)\n        ]\n\n        init() {\n            storage = SemanticTokenStorage()\n            storage.setData(SemanticTokens(tokens: semanticTokens))\n            #expect(storage.state?.tokens == semanticTokens)\n        }\n\n        @Test\n        func applyEmptyDeltasNoChange() throws {\n            let deltas = try makeDelta(resultId: \"1\", edits: [])\n\n            _ = storage.applyDelta(deltas)\n\n            let state = try #require(storage.state)\n            #expect(state.tokens.count == 3)\n            #expect(state.resultId == \"1\")\n            #expect(state.tokens == semanticTokens)\n        }\n\n        @Test\n        func applyInsertDeltas() throws {\n            let deltas = try makeDelta(resultId: \"1\", edits: [.init(start: 0, deleteCount: 0, data: [0, 2, 3, 0, 1])])\n\n            _ = storage.applyDelta(deltas)\n\n            let state = try #require(storage.state)\n            #expect(state.tokens.count == 4)\n            #expect(storage.lastResultId == \"1\")\n\n            // Should have inserted one at the beginning\n            #expect(state.tokens[0].line == 0)\n            #expect(state.tokens[0].char == 2)\n            #expect(state.tokens[0].length == 3)\n            #expect(state.tokens[0].modifiers == 1)\n\n            // We inserted a delta into the space before this one (at char 2) so this one starts at the same spot\n            #expect(state.tokens[1] == SemanticToken(line: 0, char: 2, length: 10, type: 0, modifiers: 0))\n            #expect(state.tokens[2] == semanticTokens[1])\n            #expect(state.tokens[3] == semanticTokens[2])\n        }\n\n        @Test\n        func applyDeleteOneDeltas() throws {\n            // Delete the second token (semanticTokens[1]) from the initial state.\n            // Each token is represented by 5 numbers, so token[1] starts at raw data index 5.\n            let deltas = try makeDelta(resultId: \"2\", edits: [.init(start: 5, deleteCount: 5, data: [])])\n            _ = storage.applyDelta(deltas)\n\n            let state = try #require(storage.state)\n            #expect(state.tokens.count == 2)\n            #expect(state.resultId == \"2\")\n            // The remaining tokens should be the first and third tokens, except we deleted one line between them\n            // so the third token's line is less one\n            #expect(state.tokens[0] == semanticTokens[0])\n            #expect(state.tokens[1] == SemanticToken(line: 2, char: 8, length: 10, type: 1, modifiers: 0))\n        }\n\n        @Test\n        func applyDeleteManyDeltas() throws {\n            // Delete the first two tokens from the initial state.\n            // Token[0] and token[1] together use 10 integers.\n            let deltas = try makeDelta(resultId: \"3\", edits: [.init(start: 0, deleteCount: 10, data: [])])\n            _ = storage.applyDelta(deltas)\n\n            let state = try #require(storage.state)\n            #expect(state.tokens.count == 1)\n            #expect(state.resultId == \"3\")\n            // The only remaining token should be the original third token.\n            #expect(state.tokens[0] == SemanticToken(line: 2, char: 8, length: 10, type: 1, modifiers: 0))\n        }\n\n        @Test\n        func applyInsertAndDeleteDeltas() throws {\n            // Combined test: insert a token at the beginning and delete the last token.\n            // Edit 1: Insert a new token at the beginning.\n            let insertion = DeltaEdit(start: 0, deleteCount: 0, data: [0, 2, 3, 0, 1])\n            // Edit 2: Delete the token that starts at raw data index 10 (the third token in the original state).\n            let deletion = DeltaEdit(start: 10, deleteCount: 5, data: [])\n            let deltas = try makeDelta(resultId: \"4\", edits: [insertion, deletion])\n            _ = storage.applyDelta(deltas)\n\n            let state = try #require(storage.state)\n            #expect(state.tokens.count == 3)\n            #expect(storage.lastResultId == \"4\")\n            // The new inserted token becomes the first token.\n            #expect(state.tokens[0] == SemanticToken(line: 0, char: 2, length: 3, type: 0, modifiers: 1))\n            // The original first token is shifted (its character offset increased by 2).\n            #expect(state.tokens[1] == SemanticToken(line: 0, char: 2, length: 10, type: 0, modifiers: 0))\n            // The second token from the original state remains unchanged.\n            #expect(state.tokens[2] == semanticTokens[1])\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/Search/FuzzySearch/FuzzySearchTests.swift",
    "content": "//\n//  FuzzySearchTests.swift\n//  CodeEditTests\n//\n//  Created by Tommy Ludwig on 03.02.24.\n//\n\nimport XCTest\n@testable import CodeEdit\n\nfinal class FuzzySearchTests: XCTestCase {\n    func testNormalisation() {\n        XCTAssertEqual(\"ü\".normalise()[0].normalisedContent, \"u\")\n        XCTAssertEqual(\"ñ\".normalise()[0].normalisedContent, \"n\")\n        XCTAssertEqual(\"é\".normalise()[0].normalisedContent, \"e\")\n    }\n\n    func testFuzzyMatchWeight() {\n        guard let url = URL(string: \"path/ContentView.swift\") else {\n            XCTFail(\"URL could not be created\")\n            return\n        }\n        XCTAssert(url.fuzzyMatch(query: \"CV\").weight > 0)\n        XCTAssert(url.fuzzyMatch(query: \"conv\").weight > 0)\n        XCTAssert(url.fuzzyMatch(query: \"sw\").weight > 0)\n        XCTAssert(url.fuzzyMatch(query: \"path\").weight == 0)\n    }\n\n    func testFuzzyMatchRange() {\n        guard let url = URL(string: \"path/ContentView.swift\") else {\n            XCTFail(\"URL could not be created\")\n            return\n        }\n        let range = url.fuzzyMatch(query: \"ConVie\").matchedParts\n\n        XCTAssertEqual(url.lastPathComponent[range[0]], \"Con\")\n        XCTAssertEqual(url.lastPathComponent[range[1]], \"Vie\")\n    }\n\n    func testFuzzySearch() async {\n        let urls = [\n            URL(string: \"FuzzySearchable.swift\")!,\n            URL(string: \"ContentView.swift\")!,\n            URL(string: \"FuzzyMatch.swift\")!\n        ]\n        let fuzzyMatchResult = await urls.fuzzySearch(query: \"mch\").map {\n            $0.item\n        }\n        XCTAssertEqual(fuzzyMatchResult[0].lastPathComponent, \"FuzzyMatch.swift\")\n\n        let contentViewResult = await urls.fuzzySearch(query: \"CV\").map {\n            $0.item\n        }\n        XCTAssertEqual(contentViewResult[0].lastPathComponent, \"ContentView.swift\")\n\n        let fuzzySearchableResult = await urls.fuzzySearch(query: \"seable\").map {\n            $0.item\n        }\n        XCTAssertEqual(fuzzySearchableResult[0].lastPathComponent, \"FuzzySearchable.swift\")\n\n        let swiftResults = await urls.fuzzySearch(query: \"swif\").map {\n            $0.item\n        }\n        XCTAssertEqual(swiftResults.count, 3)\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/SourceControl/GitClientTests.swift",
    "content": "//\n//  GitClientTests.swift\n//  CodeEditTests\n//\n//  Created by Khan Winter on 9/11/25.\n//\n\nimport Testing\n@testable import CodeEdit\n\n@Suite\nstruct GitClientTests {\n    @Test\n    func statusParseNullAtEnd() throws {\n        try withTempDir { dirURL in\n            // swiftlint:disable:next line_length\n            let string = \"1 .M N... 100644 100644 100644 eaef31cfa2a22418c00d7477da0b7151d122681e eaef31cfa2a22418c00d7477da0b7151d122681e CodeEdit/Features/SourceControl/Client/GitClient+Status.swift\\01 AM N... 000000 100644 100644 0000000000000000000000000000000000000000 e0f5ce250b32cf6610a284b7a33ac114079f5159 CodeEditTests/Features/SourceControl/GitClientTests.swift\\0\"\n            let client = GitClient(directoryURL: dirURL, shellClient: .live())\n            let status = try client.parseStatusString(string)\n\n            #expect(status.changedFiles.count == 2)\n            // No null string at the end\n            #expect(status.changedFiles[0].fileURL.lastPathComponent == \"GitClient+Status.swift\")\n            #expect(status.changedFiles[1].fileURL.lastPathComponent == \"GitClientTests.swift\")\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/Tasks/CEActiveTaskTests.swift",
    "content": "//\n//  CEActiveTaskTests.swift\n//  CodeEditTests\n//\n//  Created by Tommy Ludwig on 08.07.24.\n//\n\nimport Testing\n@testable import CodeEdit\n\n@MainActor\n@Suite(.serialized)\nclass CEActiveTaskTests {\n    var task: CETask\n    var activeTask: CEActiveTask\n\n    init() {\n        task = CETask(\n            name: \"Test Task\",\n            command: \"echo $STATE\",\n            environmentVariables: [CETask.EnvironmentVariable(key: \"STATE\", value: \"Testing\")]\n        )\n        activeTask = CEActiveTask(task: task)\n    }\n\n    @Test\n    func testInitialization() throws {\n        #expect(activeTask.task == task, \"Active task should be initialized with the provided CETask.\")\n    }\n\n    @Test(arguments: [Shell.zsh, Shell.bash])\n    func testRunMethod(_ shell: Shell) async throws {\n        activeTask.run(workspaceURL: nil, shell: shell)\n        await waitForExpectation(timeout: .seconds(10)) {\n            activeTask.status == .running\n        } onTimeout: {\n            Issue.record(\"Task never started. \\(activeTask.status)\")\n        }\n        activeTask.waitForExit()\n\n        await waitForExpectation(timeout: .seconds(10)) {\n            activeTask.status == .finished\n        } onTimeout: {\n            Issue.record(\"Status never changed to finished. \\(activeTask.status)\")\n        }\n\n        let output = try #require(activeTask.output)\n        #expect(output.getBufferAsString().contains(\"Testing\"))\n    }\n\n    @Test(arguments: [Shell.zsh, Shell.bash])\n    func testHandleProcessFinished(_ shell: Shell) async throws {\n        task.command = \"aNon-existentCommand\"\n        activeTask.run(workspaceURL: nil, shell: shell)\n        activeTask.waitForExit()\n\n        await waitForExpectation(timeout: .seconds(10)) {\n            activeTask.status == .failed\n        } onTimeout: {\n            Issue.record(\"Status never changed to failed. \\(activeTask.status)\")\n        }\n    }\n\n    @Test(arguments: [Shell.zsh, Shell.bash])\n    func testClearOutput(_ shell: Shell) async throws {\n        activeTask.run(workspaceURL: nil, shell: shell)\n        activeTask.waitForExit()\n\n        await waitForExpectation {\n            activeTask.status == .finished\n        } onTimeout: {\n            Issue.record(\"Status never changed to finished.\")\n        }\n\n        #expect(\n            activeTask.output?.getBufferAsString().isEmpty == false,\n            \"Task output should not be empty after task completion.\"\n        )\n        activeTask.clearOutput()\n\n        await waitForExpectation {\n            activeTask.output?.getBufferAsString().isEmpty == true\n        } onTimeout: {\n            Issue.record(\"Task output should be empty after clearOutput is called.\")\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/Tasks/TaskManagerTests.swift",
    "content": "//\n//  TaskManagerTests.swift\n//  CodeEditTests\n//\n//  Created by Tommy Ludwig on 08.07.24.\n//\n\nimport Foundation\nimport Testing\n@testable import CodeEdit\n\n@MainActor\n@Suite(.serialized)\nclass TaskManagerTests {\n    var taskManager: TaskManager!\n    var mockWorkspaceSettings: CEWorkspaceSettingsData!\n\n    init() throws {\n        let workspaceSettings = try JSONDecoder().decode(CEWorkspaceSettingsData.self, from: Data(\"{}\".utf8))\n        mockWorkspaceSettings = workspaceSettings\n        taskManager = TaskManager(workspaceSettings: mockWorkspaceSettings, workspaceURL: nil)\n    }\n\n    func testInitialization() {\n        #expect(taskManager != nil)\n        #expect(taskManager.availableTasks == mockWorkspaceSettings.tasks)\n    }\n\n    @Test\n    func executeTaskInZsh() async throws {\n        Settings.shared.preferences.terminal.shell = .zsh\n\n        let task = CETask(name: \"Test Task\", command: \"echo 'Hello World'\")\n        mockWorkspaceSettings.tasks.append(task)\n        taskManager.selectedTaskID = task.id\n        taskManager.executeActiveTask()\n\n        await waitForExpectation(timeout: .seconds(10)) {\n            self.taskManager.activeTasks[task.id]?.status == .finished\n        } onTimeout: {\n            Issue.record(\"Status never changed to finished.\")\n        }\n\n        let outputString = try #require(taskManager.activeTasks[task.id]?.output?.getBufferAsString())\n        #expect(outputString.contains(\"Hello World\"))\n    }\n\n    @Test\n    func executeTaskInBash() async throws {\n        Settings.shared.preferences.terminal.shell = .bash\n\n        let task = CETask(name: \"Test Task\", command: \"echo 'Hello World'\")\n        mockWorkspaceSettings.tasks.append(task)\n        taskManager.selectedTaskID = task.id\n        taskManager.executeActiveTask()\n\n        await waitForExpectation(timeout: .seconds(10)) {\n            self.taskManager.activeTasks[task.id]?.status == .finished\n        } onTimeout: {\n            Issue.record(\"Status never changed to finished.\")\n        }\n\n        let outputString = try #require(taskManager.activeTasks[task.id]?.output?.getBufferAsString())\n        #expect(outputString.contains(\"Hello World\"))\n    }\n\n    @Test(.disabled(\"Not sure why but tasks run in shells seem to never receive signals.\"))\n    func terminateSelectedTask() async throws {\n        let task = CETask(name: \"Test Task\", command: \"sleep 10\")\n        mockWorkspaceSettings.tasks.append(task)\n        taskManager.selectedTaskID = task.id\n        taskManager.executeActiveTask()\n\n        await waitForExpectation {\n            taskManager.taskStatus(taskID: task.id) == .running\n        } onTimeout: {\n            Issue.record(\"Task did not run\")\n        }\n\n        taskManager.terminateActiveTask()\n\n        await waitForExpectation(timeout: .seconds(10)) {\n            taskManager.taskStatus(taskID: task.id) == .notRunning\n        } onTimeout: {\n            Issue.record(\"Task did not terminate. \\(taskManager.taskStatus(taskID: task.id))\")\n        }\n    }\n\n    // This test verifies the functionality of suspending and resuming a task.\n    // It ensures that suspend signals do not stack up,\n    // meaning only one resume signal is required to resume the task,\n    // regardless of the number of times `suspendTask()` is called.\n    @Test(.disabled(\"Not sure why but tasks run in shells seem to never receive signals.\"))\n    func suspendAndResumeTask() async throws {\n        let task = CETask(name: \"Test Task\", command: \"sleep 5\")\n        mockWorkspaceSettings.tasks.append(task)\n        taskManager.selectedTaskID = task.id\n        taskManager.executeActiveTask()\n\n        await waitForExpectation {\n            taskManager.taskStatus(taskID: task.id) == .running\n        } onTimeout: {\n            Issue.record(\"Task did not start running.\")\n        }\n        taskManager.suspendTask(taskID: task.id)\n\n        await waitForExpectation {\n            taskManager.taskStatus(taskID: task.id) == .stopped\n        } onTimeout: {\n            Issue.record(\"Task did not suspend\")\n        }\n        taskManager.resumeTask(taskID: task.id)\n\n        await waitForExpectation {\n            taskManager.taskStatus(taskID: task.id) == .running\n        } onTimeout: {\n            Issue.record(\"Task did not resume\")\n        }\n\n        taskManager.suspendTask(taskID: task.id)\n        taskManager.suspendTask(taskID: task.id)\n        taskManager.suspendTask(taskID: task.id)\n\n        await waitForExpectation {\n            taskManager.taskStatus(taskID: task.id) == .stopped\n        } onTimeout: {\n            Issue.record(\"Task did not suspend after multiple suspend messages.\")\n        }\n        taskManager.resumeTask(taskID: task.id)\n\n        await waitForExpectation {\n            taskManager.taskStatus(taskID: task.id) == .running\n        } onTimeout: {\n            Issue.record(\"Task did not resume after multiple suspend messages.\")\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/TerminalEmulator/ShellIntegrationTests.swift",
    "content": "//\n//  ShellIntegrationTests.swift\n//  CodeEditTests\n//\n//  Created by Khan Winter on 6/11/24.\n//\n\nimport Foundation\nimport SwiftUI\nimport XCTest\n@testable import CodeEdit\n\nfinal class ShellIntegrationTests: XCTestCase {\n    func testBash() throws {\n        var environment: [String] = []\n        let args = try ShellIntegration.setUpIntegration(\n            for: .bash,\n            environment: &environment,\n            useLogin: false,\n            interactive: true\n        )\n        XCTAssertTrue(\n            environment.contains(\"\\(ShellIntegration.Variables.ceInjection)=1\"), \"Does not contain injection flag\"\n        )\n        XCTAssertTrue(\n            !environment.contains(\"\\(ShellIntegration.Variables.shellLogin)=1\"), \"Should not contain login flag\"\n        )\n        XCTAssertTrue(args.contains(\"--init-file\"), \"No init file flag\")\n        XCTAssertTrue(\n            args.contains(where: { $0.hasSuffix(\"/codeedit_shell_integration.bash\") }), \"No setup file provided in args\"\n        )\n        XCTAssertTrue(args.contains(\"-i\"), \"No interactive flag found\")\n    }\n\n    func testBashLogin() throws {\n        var environment: [String] = []\n        let args = try ShellIntegration.setUpIntegration(\n            for: .bash,\n            environment: &environment,\n            useLogin: true,\n            interactive: true\n        )\n        XCTAssertTrue(\n            environment.contains(\"\\(ShellIntegration.Variables.ceInjection)=1\"), \"Does not contain injection flag\"\n        )\n        XCTAssertTrue(environment.contains(\"\\(ShellIntegration.Variables.shellLogin)=1\"), \"Does not contain login flag\")\n        XCTAssertTrue(args.contains(\"--init-file\"), \"No init file flag\")\n        XCTAssertTrue(\n            args.contains(where: { $0.hasSuffix(\"/codeedit_shell_integration.bash\") }), \"No setup file provided in args\"\n        )\n        XCTAssertTrue(args.contains(\"-il\"), \"No interactive login flag found\")\n    }\n\n    func testZsh() throws {\n        var environment: [String] = []\n        let args = try ShellIntegration.setUpIntegration(\n            for: .zsh,\n            environment: &environment,\n            useLogin: false,\n            interactive: true\n        )\n        XCTAssertTrue(args.contains(\"-i\"), \"Interactive flag\")\n        XCTAssertTrue(!args.contains(\"-il\"), \"No Interactive/Login flag\")\n\n        XCTAssertTrue(\n            environment.contains(\"\\(ShellIntegration.Variables.ceInjection)=1\"), \"Does not contain injection flag\"\n        )\n        XCTAssertTrue(\n            !environment.contains(\"\\(ShellIntegration.Variables.shellLogin)=1\"), \"Should not contain login flag\"\n        )\n        XCTAssertTrue(environment.contains(where: { $0.hasPrefix(ShellIntegration.Variables.zDotDir) }))\n        XCTAssertTrue(environment.contains(where: { $0.hasPrefix(ShellIntegration.Variables.userZDotDir) }))\n        // Should not use this one\n        XCTAssertTrue(!environment.contains(where: { $0.hasPrefix(ShellIntegration.Variables.ceZDotDir) }))\n\n        guard var tempDir = environment.first(where: { $0.hasPrefix(ShellIntegration.Variables.zDotDir) }) else {\n            XCTFail(\"No temp dir\")\n            return\n        }\n        // trim \"ZDOTDIR=\" from var\n        tempDir = String(tempDir.dropFirst(ShellIntegration.Variables.zDotDir.count + 1))\n        XCTAssertTrue(FileManager.default.fileExists(atPath: tempDir.appending(\"/.zshrc\")))\n        XCTAssertTrue(FileManager.default.fileExists(atPath: tempDir.appending(\"/.zprofile\")))\n        XCTAssertTrue(FileManager.default.fileExists(atPath: tempDir.appending(\"/.zlogin\")))\n        XCTAssertTrue(FileManager.default.fileExists(atPath: tempDir.appending(\"/.zshenv\")))\n    }\n\n    func testZshLogin() throws {\n        var environment: [String] = []\n        let args = try ShellIntegration.setUpIntegration(\n            for: .zsh,\n            environment: &environment,\n            useLogin: true,\n            interactive: true\n        )\n        XCTAssertTrue(!args.contains(\"-i\"), \"No Interactive flag\")\n        XCTAssertTrue(args.contains(\"-il\"), \"Interactive/Login flag\")\n\n        XCTAssertTrue(\n            environment.contains(\"\\(ShellIntegration.Variables.ceInjection)=1\"), \"Does not contain injection flag\"\n        )\n        XCTAssertTrue(\n            environment.contains(\"\\(ShellIntegration.Variables.shellLogin)=1\"), \"Does not contain login flag\"\n        )\n        XCTAssertTrue(environment.contains(where: { $0.hasPrefix(ShellIntegration.Variables.zDotDir) }))\n        XCTAssertTrue(environment.contains(where: { $0.hasPrefix(ShellIntegration.Variables.userZDotDir) }))\n        // Should not use this one\n        XCTAssertTrue(!environment.contains(where: { $0.hasPrefix(ShellIntegration.Variables.ceZDotDir) }))\n\n        guard var tempDir = environment.first(where: { $0.hasPrefix(ShellIntegration.Variables.zDotDir) }) else {\n            XCTFail(\"No temp dir\")\n            return\n        }\n        // trim \"ZDOTDIR=\" from var\n        tempDir = String(tempDir.dropFirst(ShellIntegration.Variables.zDotDir.count + 1))\n        XCTAssertTrue(FileManager.default.fileExists(atPath: tempDir.appending(\"/.zshrc\")))\n        XCTAssertTrue(FileManager.default.fileExists(atPath: tempDir.appending(\"/.zprofile\")))\n        XCTAssertTrue(FileManager.default.fileExists(atPath: tempDir.appending(\"/.zlogin\")))\n        XCTAssertTrue(FileManager.default.fileExists(atPath: tempDir.appending(\"/.zshenv\")))\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/UtilityArea/UtilityAreaViewModelTests.swift",
    "content": "//\n//  UtilityAreaViewModelTests.swift\n//  CodeEditTests\n//\n//  Created by Khan Winter on 1/7/25.\n//\n\nimport XCTest\n@testable import CodeEdit\n\nfinal class UtilityAreaViewModelTests: XCTestCase {\n    var model: UtilityAreaViewModel!\n    let rootURL: URL = URL(filePath: \"~/\")\n\n    override func setUp() {\n        model = UtilityAreaViewModel()\n        model.terminals = [\n            UtilityAreaTerminal(id: UUID(), url: rootURL, title: \"Terminal 1\", shell: .bash),\n            UtilityAreaTerminal(id: UUID(), url: rootURL, title: \"Terminal 2\", shell: .zsh),\n            UtilityAreaTerminal(id: UUID(), url: rootURL, title: \"Terminal 3\", shell: nil),\n            UtilityAreaTerminal(id: UUID(), url: rootURL, title: \"Terminal 4\", shell: .bash),\n            UtilityAreaTerminal(id: UUID(), url: rootURL, title: \"Terminal 5\", shell: .zsh)\n        ]\n    }\n\n    func testRemoveLastTerminal() {\n        let originalTerminals = model.terminals.map { $0.id }\n        model.removeTerminals(Set([originalTerminals[4]]))\n        XCTAssertEqual(model.terminals.count, 4)\n        XCTAssertEqual(Array(originalTerminals[0..<4]), model.terminals.map({ $0.id }))\n    }\n\n    func testRemoveMiddleTerminal() {\n        let originalTerminals = model.terminals.map { $0.id }\n        model.removeTerminals(Set([originalTerminals[2]]))\n        XCTAssertEqual(model.terminals.count, 4)\n        XCTAssertEqual(\n            Array(originalTerminals[0..<2]) + Array(originalTerminals[3..<5]),\n            model.terminals.map({ $0.id })\n        )\n    }\n\n    func testRemoveFirstTerminal() {\n        let originalTerminals = model.terminals.map { $0.id }\n        model.removeTerminals(Set([originalTerminals[0]]))\n        XCTAssertEqual(model.terminals.count, 4)\n        XCTAssertEqual(Array(originalTerminals[1..<5]), model.terminals.map({ $0.id }))\n    }\n\n    func testRemoveAllTerminals() {\n        let originalTerminals = model.terminals.map { $0.id }\n        model.removeTerminals(Set(originalTerminals))\n        XCTAssertEqual(model.terminals, [])\n    }\n\n    // Skipping this test. The semantics of updating terminal titles needs work.\n    func _testUpdateTerminalTitle() {\n        model.updateTerminal(model.terminals[0].id, title: \"Custom Title\")\n        XCTAssertFalse(model.terminals[0].customTitle) // This feels wrong, but it's how this view model is set up.\n        XCTAssertEqual(model.terminals[0].title, \"Custom Title\")\n\n        model.updateTerminal(model.terminals[0].id, title: nil)\n        XCTAssertFalse(model.terminals[0].customTitle)\n        // Should stay the same title, just disables the custom title.\n        XCTAssertEqual(model.terminals[0].title, \"Custom Title\")\n    }\n\n    func testAddTerminal() {\n        model.addTerminal(shell: nil, rootURL: rootURL)\n        XCTAssertEqual(model.terminals.count, 6)\n        XCTAssertEqual(model.terminals[5].shell, nil)\n        XCTAssertEqual(model.terminals[5].title, \"terminal\")\n        XCTAssertFalse(model.terminals[5].customTitle)\n        XCTAssertEqual(model.terminals[5].url, rootURL)\n    }\n\n    func testAddTerminalCustomShell() {\n        model.addTerminal(shell: .bash, rootURL: rootURL)\n        XCTAssertEqual(model.terminals.count, 6)\n        XCTAssertEqual(model.terminals[5].shell, .bash)\n        XCTAssertEqual(model.terminals[5].title, Shell.bash.rawValue)\n        XCTAssertFalse(model.terminals[5].customTitle)\n        XCTAssertEqual(model.terminals[5].url, rootURL)\n\n        model.addTerminal(shell: .zsh, rootURL: rootURL)\n        XCTAssertEqual(model.terminals.count, 7)\n        XCTAssertEqual(model.terminals[6].shell, .zsh)\n        XCTAssertEqual(model.terminals[6].title, Shell.zsh.rawValue)\n        XCTAssertFalse(model.terminals[6].customTitle)\n        XCTAssertEqual(model.terminals[6].url, rootURL)\n    }\n\n    func testReplaceTerminal() {\n        let terminalToReplace = model.terminals[2]\n        model.replaceTerminal(terminalToReplace.id)\n        XCTAssertNotEqual(model.terminals[2].id, terminalToReplace.id)\n        XCTAssertEqual(model.terminals[2].shell, terminalToReplace.shell)\n        XCTAssertEqual(model.terminals[2].url, terminalToReplace.url)\n    }\n\n    func testInitializeTerminals() {\n        let terminals = model.terminals\n        model.initializeTerminals(workspaceURL: rootURL)\n        XCTAssertEqual(terminals, model.terminals) // Should not modify if terminals exist\n\n        // Remove all terminals so it can do something\n        model.removeTerminals(Set(model.terminals.map { $0.id }))\n        XCTAssertEqual(model.terminals.count, 0, \"Model did not delete all terminals\")\n\n        model.initializeTerminals(workspaceURL: rootURL)\n        XCTAssertEqual(model.terminals.count, 1)\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/Welcome/RecentProjectsTests.swift",
    "content": "//\n//  RecentsStoreTests.swift\n//  CodeEditTests\n//\n//  Created by Khan Winter on 5/27/25.\n//  Updated for the new RecentsStore on 6/08/25\n//\n\nimport Testing\nimport Foundation\n@testable import WelcomeWindow   // <- contains RecentsStore\n\n// -----------------------------------------------------------------------------\n// MARK: - helpers\n// -----------------------------------------------------------------------------\n\nprivate let testDefaults: UserDefaults = {\n    let name = \"RecentsStoreTests.\\(UUID())\"\n    let userDefaults   = UserDefaults(suiteName: name)!\n    userDefaults.removePersistentDomain(forName: name)   // start clean\n    return userDefaults\n}()\n\nprivate extension URL {\n    /// Creates an empty file (or directory) on disk, so we can successfully\n    /// generate security-scoped bookmarks for it.\n    ///\n    /// - parameter directory: Pass `true` to create a directory, `false`\n    ///                        to create a regular file.\n    func materialise(directory: Bool) throws {\n        let fileManager = FileManager.default\n        if directory {\n            try fileManager.createDirectory(at: self, withIntermediateDirectories: true)\n        } else {\n            fileManager.createFile(atPath: path, contents: Data())\n        }\n    }\n\n    /// Convenience that returns a fresh URL inside the per-suite temp dir.\n    static func temp(named name: String, directory: Bool) -> URL {\n        TestContext.tempRoot.appendingPathComponent(\n            name,\n            isDirectory: directory\n        )\n    }\n}\n\n@MainActor\nprivate func clear() {\n    RecentsStore.clearList()\n    testDefaults.removeObject(forKey: \"recentProjectBookmarks\")\n}\n\n/// A container for values that need to remain alive for the whole test-suite.\nprivate enum TestContext {\n    /// Every run gets its own random temp folder that is cleaned up\n    /// when the process exits.\n    static let tempRoot: URL = {\n        let root = FileManager.default.temporaryDirectory\n            .appendingPathComponent(\"RecentsStoreTests_\\(UUID())\", isDirectory: true)\n        try? FileManager.default.createDirectory(\n            at: root,\n            withIntermediateDirectories: true\n        )\n        atexit_b {\n            try? FileManager.default.removeItem(at: root)\n        }\n        return root\n    }()\n}\n\n// -----------------------------------------------------------------------------\n// MARK: - Test-suite\n// -----------------------------------------------------------------------------\n\n// Needs to be serial – everything writes to `UserDefaults.standard`.\n@Suite(.serialized)\n@MainActor\nclass RecentsStoreTests {\n\n    init() {\n        // Redirect the store to the throw-away suite.\n        RecentsStore.defaults = testDefaults\n        clear()\n    }\n\n    deinit {\n        Task { @MainActor in\n            clear()\n        }\n    }\n\n    // -------------------------------------------------------------------------\n    // MARK: - Tests mirroring the old suite\n    // -------------------------------------------------------------------------\n\n    @Test\n    func newStoreEmpty() {\n        clear()\n        #expect(RecentsStore.recentProjectURLs().isEmpty)\n    }\n\n    @Test\n    func savesURLs() throws {\n        clear()\n        let dir  = URL.temp(named: \"Directory\", directory: true)\n        let file = URL.temp(named: \"Directory/file.txt\", directory: false)\n\n        try dir.materialise(directory: true)\n        try file.materialise(directory: false)\n\n        RecentsStore.documentOpened(at: dir)\n        RecentsStore.documentOpened(at: file)\n\n        let recents = RecentsStore.recentProjectURLs()\n        #expect(recents.count == 2)\n        #expect(recents[0].standardizedFileURL == file.standardizedFileURL)\n        #expect(recents[1].standardizedFileURL == dir.standardizedFileURL)\n    }\n\n    @Test\n    func clearURLs() throws {\n        clear()\n        let dir  = URL.temp(named: \"Directory\", directory: true)\n        let file = URL.temp(named: \"Directory/file.txt\", directory: false)\n\n        try dir.materialise(directory: true)\n        try file.materialise(directory: false)\n\n        RecentsStore.documentOpened(at: dir)\n        RecentsStore.documentOpened(at: file)\n        #expect(!RecentsStore.recentProjectURLs().isEmpty)\n\n        RecentsStore.clearList()\n        #expect(RecentsStore.recentProjectURLs().isEmpty)\n    }\n\n    @Test\n    func duplicatesAreMovedToFront() throws {\n        clear()\n        let dir  = URL.temp(named: \"Directory\", directory: true)\n        let file = URL.temp(named: \"Directory/file.txt\", directory: false)\n\n        try dir.materialise(directory: true)\n        try file.materialise(directory: false)\n\n        RecentsStore.documentOpened(at: dir)\n        RecentsStore.documentOpened(at: file)\n\n        // Open `dir` again → should move to front\n        RecentsStore.documentOpened(at: dir)\n        // Open duplicate again (no change in order, still unique)\n        RecentsStore.documentOpened(at: dir)\n\n        let recents = RecentsStore.recentProjectURLs()\n        #expect(recents.count == 2)\n        #expect(recents[0].standardizedFileURL == dir.standardizedFileURL)\n        #expect(recents[1].standardizedFileURL == file.standardizedFileURL)\n    }\n\n    @Test\n    func removeSubset() throws {\n        clear()\n        let dir  = URL.temp(named: \"Directory\", directory: true)\n        let file = URL.temp(named: \"Directory/file.txt\", directory: false)\n\n        try dir.materialise(directory: true)\n        try file.materialise(directory: false)\n\n        RecentsStore.documentOpened(at: dir)\n        RecentsStore.documentOpened(at: file)\n\n        let remaining = RecentsStore.removeRecentProjects([dir])\n\n        #expect(remaining.count == 1)\n        #expect(remaining[0].standardizedFileURL == file.standardizedFileURL)\n\n        let recents = RecentsStore.recentProjectURLs()\n        #expect(recents.count == 1)\n        #expect(recents[0].standardizedFileURL == file.standardizedFileURL)\n\n    }\n\n    @Test\n    func maxesOutAt100Items() throws {\n        clear()\n        for idx in 0..<101 {\n            let isDir = Bool.random()\n            let name = \"entry_\\(idx)\" + (isDir ? \"\" : \".txt\")\n            let url = URL.temp(named: name, directory: isDir)\n            try url.materialise(directory: isDir)\n            RecentsStore.documentOpened(at: url)\n        }\n        #expect(RecentsStore.recentProjectURLs().count == 100)\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Features/WorkspaceSettings/CEWorkspaceSettingsTests.swift",
    "content": "//\n//  CEWorkspaceSettingsTests.swift\n//  CodeEditTests\n//\n//  Created by Khan Winter on 4/21/25.\n//\n\nimport Foundation\nimport Testing\n@testable import CodeEdit\n\nstruct CEWorkspaceSettingsTests {\n    let settings: CEWorkspaceSettings = CEWorkspaceSettings(workspaceURL: URL(filePath: \"/\"))\n\n    @Test\n    func settingsURLNoSpace() async throws {\n        #expect(settings.folderURL.lastPathComponent == \".codeedit\")\n        #expect(settings.settingsURL.lastPathComponent == \"settings.json\")\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Utils/CEWorkspaceFileManager/CEWorkspaceFileManagerTests.swift",
    "content": "//\n//  UnitTests.swift\n//  CodeEditModules/WorkspaceClient\n//\n//  Created by Marco Carnevali on 16/03/22.\n//\nimport Combine\nimport Foundation\nimport XCTest\n@testable import CodeEdit\n\nfinal class CEWorkspaceFileManagerUnitTests: XCTestCase {\n    let typeOfExtensions = [\"json\", \"txt\", \"swift\", \"js\", \"py\", \"md\"]\n    var directory: URL!\n\n    class DummyObserver: CEWorkspaceFileManagerObserver {\n        var completion: (() -> Void)?\n\n        init(completion: @escaping () -> Void) {\n            self.completion = completion\n        }\n\n        func fileManagerUpdated(updatedItems: Set<CEWorkspaceFile>) {\n            completion?()\n        }\n    }\n\n    override func setUp() async throws {\n        directory = try FileManager.default.url(\n            for: .developerApplicationDirectory,\n            in: .userDomainMask,\n            appropriateFor: nil,\n            create: true\n        )\n        .appending(path: \"CodeEdit\", directoryHint: .isDirectory)\n        .appending(path: \"WorkspaceClientTests\", directoryHint: .isDirectory)\n        try? FileManager.default.removeItem(at: directory)\n        try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)\n    }\n\n    override func tearDown() async throws {\n        try? FileManager.default.removeItem(at: directory)\n    }\n\n    func testListFile() throws {\n        let randomCount = Int.random(in: 1 ... 100)\n        let files = generateRandomFiles(amount: randomCount)\n        try files.forEach {\n            let fakeData = Data(\"fake string\".utf8)\n            let fileUrl = directory\n                .appending(path: $0)\n            try fakeData.write(to: fileUrl)\n        }\n        let client = CEWorkspaceFileManager(\n            folderUrl: directory,\n            ignoredFilesAndFolders: [],\n            sourceControlManager: nil\n        )\n\n        // Compare to flattened files - 1 cause root is in there\n        XCTAssertEqual(files.count, client.flattenedFileItems.count - 1)\n        try FileManager.default.removeItem(at: directory)\n    }\n\n    func testDirectoryChanges() throws {\n        let client = CEWorkspaceFileManager(\n            folderUrl: directory,\n            ignoredFilesAndFolders: [],\n            sourceControlManager: nil\n        )\n\n        let newFile = generateRandomFiles(amount: 1)[0]\n        let expectation = XCTestExpectation(description: \"wait for files\")\n\n        let observer = DummyObserver {\n            let url = client.folderUrl.appending(path: newFile).path\n            if client.flattenedFileItems[url] != nil {\n                expectation.fulfill()\n            }\n        }\n        client.addObserver(observer)\n\n        var files = client.flattenedFileItems.map { $0.value.name }\n        files.append(newFile)\n        try files.forEach {\n            let fakeData = Data(\"fake string\".utf8)\n            let fileUrl = directory\n                .appending(path: $0)\n            try fakeData.write(to: fileUrl)\n        }\n\n        wait(for: [expectation], timeout: 2.0)\n        XCTAssertEqual(files.count, client.flattenedFileItems.count - 1)\n        try FileManager.default.removeItem(at: directory)\n        client.removeObserver(observer)\n    }\n\n    func generateRandomFiles(amount: Int) -> [String] {\n        [String](repeating: \"\", count: amount)\n            .map { _ in\n                let fileName = randomString(length: Int.random(in: 1 ... 100))\n                let fileExtension = typeOfExtensions[Int.random(in: 0 ..< typeOfExtensions.count)]\n                return \"\\(fileName).\\(fileExtension)\"\n            }\n    }\n\n    func randomString(length: Int) -> String {\n        let letters = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\"\n        return String((0 ..< length).map { _ in letters.randomElement()! })\n    }\n\n    func testGetFile() throws {\n        let testDirectoryURL = directory.appending(path: \"level1/level-2/level3\")\n        let testFileURL = directory.appending(path: \"level1/level-2/level3/file.txt\")\n        try FileManager.default.createDirectory(at: testDirectoryURL, withIntermediateDirectories: true)\n        try \"\".write(to: testFileURL, atomically: true, encoding: .utf8)\n\n        let fileManager = CEWorkspaceFileManager(\n            folderUrl: directory,\n            ignoredFilesAndFolders: [],\n            sourceControlManager: nil\n        )\n\n        XCTAssert(fileManager.getFile(testFileURL.path()) == nil)\n        XCTAssert(fileManager.childrenOfFile(CEWorkspaceFile(url: testFileURL)) == nil)\n        XCTAssert(fileManager.getFile(testFileURL.path(), createIfNotFound: true) != nil)\n        XCTAssert(fileManager.childrenOfFile(CEWorkspaceFile(url: testDirectoryURL)) != nil)\n    }\n\n    func testDeleteFile() throws {\n        let testFileURL = directory.appending(path: \"file.txt\")\n        try \"\".write(to: testFileURL, atomically: true, encoding: .utf8)\n\n        let fileManager = CEWorkspaceFileManager(\n            folderUrl: directory,\n            ignoredFilesAndFolders: [],\n            sourceControlManager: nil\n        )\n        XCTAssert(fileManager.getFile(testFileURL.path()) != nil)\n        XCTAssert(FileManager.default.fileExists(atPath: testFileURL.path()) == true)\n        try fileManager.delete(file: CEWorkspaceFile(url: testFileURL), confirmDelete: false)\n        XCTAssert(FileManager.default.fileExists(atPath: testFileURL.path()) == false)\n    }\n\n    func testDuplicateFile() throws {\n        let testFileURL = directory.appending(path: \"file.txt\")\n        let testDuplicatedFileURL = directory.appending(path: \"file copy.txt\")\n        try \"😄\".write(to: testFileURL, atomically: true, encoding: .utf8)\n\n        let fileManager = CEWorkspaceFileManager(\n            folderUrl: directory,\n            ignoredFilesAndFolders: [],\n            sourceControlManager: nil\n        )\n        XCTAssert(fileManager.getFile(testFileURL.path()) != nil)\n        XCTAssert(FileManager.default.fileExists(atPath: testFileURL.path()) == true)\n        try fileManager.duplicate(file: CEWorkspaceFile(url: testFileURL))\n        XCTAssert(FileManager.default.fileExists(atPath: testFileURL.path()) == true)\n        XCTAssert(FileManager.default.fileExists(atPath: testDuplicatedFileURL.path(percentEncoded: false)) == true)\n        XCTAssert(try String(contentsOf: testDuplicatedFileURL) == \"😄\")\n    }\n\n    func testAddFile() throws {\n        let fileManager = CEWorkspaceFileManager(\n            folderUrl: directory,\n            ignoredFilesAndFolders: [],\n            sourceControlManager: nil\n        )\n\n        // This will throw if unsuccessful.\n        var file = try fileManager.addFile(fileName: \"Test File.txt\", toFile: fileManager.workspaceItem)\n\n        // Should not add a new file extension, it already has one. This adds a '.' at the end if incorrect.\n        // See #1966\n        XCTAssertEqual(file.name, \"Test File.txt\")\n\n        // Test the automatic file extension stuff\n        file = try fileManager.addFile(\n            fileName: \"Test File Extension\",\n            toFile: fileManager.workspaceItem,\n            useExtension: nil\n        )\n\n        // Should detect '.txt' with the previous file in the same directory.\n        XCTAssertEqual(file.name, \"Test File Extension.txt\")\n\n        // Test explicit file extension with both . and no period at the beginning of the given extension.\n        file = try fileManager.addFile(\n            fileName: \"Explicit File Extension\",\n            toFile: fileManager.workspaceItem,\n            useExtension: \"xlsx\"\n        )\n        XCTAssertEqual(file.name, \"Explicit File Extension.xlsx\")\n        file = try fileManager.addFile(\n            fileName: \"PDF\",\n            toFile: fileManager.workspaceItem,\n            useExtension: \".pdf\"\n        )\n        XCTAssertEqual(file.name, \"PDF.pdf\")\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Utils/UnitTests_Extensions.swift",
    "content": "//\n//  UnitTests.swift\n//  CodeEditModules/CodeEditUtilsTests\n//\n//  Created by Lukas Pistrol on 01.05.22.\n//\n\nimport Foundation\nimport SwiftUI\nimport XCTest\n@testable import CodeEdit\n\nfinal class CodeEditUtilsExtensionsUnitTests: XCTestCase {\n\n    // MARK: - COLOR + HEX\n\n    func testColorConversionFromHEXString() throws {\n        let colorString = \"#123456\"\n        let color = Color(hex: colorString)\n\n        XCTAssertEqual(colorString, color.hexString)\n    }\n\n    func testNSColorConversionFromHEXString() throws {\n        let colorString = \"#123456\"\n        let color = NSColor(hex: colorString)\n\n        XCTAssertEqual(colorString, color.hexString)\n    }\n\n    func testColorConversionFromHEXInt() throws {\n        let colorInt = 0x123456\n        let color = Color(hex: colorInt)\n\n        XCTAssertEqual(colorInt, color.hex)\n    }\n\n    func testNSColorConversionFromHEXInt() throws {\n        let colorInt = 0x123456\n        let color = NSColor(hex: colorInt)\n\n        XCTAssertEqual(colorInt, color.hex)\n    }\n\n    func testColorConversionAlphaValue() throws {\n        let alpha = 0.25\n        let color = Color(hex: \"#123456\", alpha: alpha)\n\n        XCTAssertEqual(alpha, color.alphaComponent)\n    }\n\n    func testNSColorConversionAlphaValue() throws {\n        let alpha = 0.25\n        let color = NSColor(hex: \"#123456\", alpha: alpha)\n\n        XCTAssertEqual(alpha, color.alphaComponent)\n    }\n\n    // MARK: - DATE + FORMATTED\n\n    func testRelativeDateStringMinutes() throws {\n        let date = Date.now.addingTimeInterval(-61)\n        let string = date.relativeStringToNow(locale: Locale(identifier: \"en_US\"))\n\n        XCTAssertEqual(\"1 min. ago\", string)\n    }\n\n    func testRelativeDateStringHours() throws {\n        let date = Date.now.addingTimeInterval(-3_601)\n        let string = date.relativeStringToNow(locale: Locale(identifier: \"en_US\"))\n\n        XCTAssertEqual(\"1 hr. ago\", string)\n    }\n\n    func testRelativeDateStringDays() throws {\n        let date = Date.now.addingTimeInterval(-86_400)\n        let string = date.relativeStringToNow(locale: Locale(identifier: \"en_US\"))\n\n        XCTAssertEqual(\"yesterday\", string)\n    }\n\n    // MARK: - STRING + MD5\n\n    func testMD5GenerationCaseSensitive() throws {\n        let testString = \"CodeEdit\"\n        let md5 = testString.md5(caseSensitive: true)\n\n        let result = \"8ba8c8fd0442f7bae4d441e2a3fda706\"\n        XCTAssertEqual(result, md5)\n    }\n\n    func testMD5Generation() throws {\n        let testString = \"CodeEdit\"\n        let md5 = testString.md5(caseSensitive: false)\n\n        let result = \"4cdf122ff382a2d929eddc1a63473ec1\"\n        XCTAssertEqual(result, md5)\n    }\n\n    // MARK: - STRING + SHA256\n\n    func testSHA256GenerationCaseSensitive() throws {\n        let testString = \"CodeEdit\"\n        let md5 = testString.sha256(caseSensitive: true)\n\n        let result = \"52125689c088f1783e53c48db78a4fe7b3fa10b12d8fba205fcf054e5ef3789a\"\n        XCTAssertEqual(result, md5)\n    }\n\n    func test256Generation() throws {\n        let testString = \"CodeEdit\"\n        let md5 = testString.sha256(caseSensitive: false)\n\n        let result = \"7c3f327eab3860fc823a99623b348afbf1d7aebaec5d21289fbaeab0f6340e4a\"\n        XCTAssertEqual(result, md5)\n    }\n\n    // MARK: - STRING + REMOVING OCCURRENCES\n\n    func testRemovingNewLines() throws {\n        let string = \"Hello, \\nWorld!\"\n        let withoutNewLines = string.removingNewLines()\n\n        let result = \"Hello, World!\"\n        XCTAssertEqual(result, withoutNewLines)\n    }\n\n    func testRemovingSpaces() throws {\n        let string = \"Hello, World!\"\n        let withoutSpaces = string.removingSpaces()\n\n        let result = \"Hello,World!\"\n        XCTAssertEqual(result, withoutSpaces)\n    }\n\n    // MARK: - STRING + VALID FILE NAME\n\n    func testValidFileName() {\n        let validCases = [\n            \"hello world\",\n            \"newSwiftFile.swift\",\n            \"documento_español.txt\",\n            \"dokument_deutsch.pdf\",\n            \"rapport_français.docx\",\n            \"レポート_日本語.xlsx\",\n            \"отчет_русский.pptx\",\n            \"보고서_한국어.txt\",\n            \"文件_中文.pdf\",\n            \"dokument_svenska.txt\",\n            \"relatório_português.docx\",\n            \"relazione_italiano.pdf\",\n            \"file_with_emoji_😊.txt\",\n            \"emoji_report_📄.pdf\",\n            \"archivo_con_emoji_🌟.docx\",\n            \"文件和表情符号_🚀.txt\",\n            \"rapport_avec_emoji_🎨.pptx\",\n            // 255 characters (exactly the maximum)\n            String((0..<255).map({ _ in \"abcd\".randomElement() ?? Character(\"\") }))\n        ]\n\n        for validCase in validCases {\n            XCTAssertTrue(validCase.isValidFilename, \"Detected invalid case \\\"\\(validCase)\\\", should be valid.\")\n        }\n    }\n\n    func testInvalidFileName() {\n        // The only limitations for macOS file extensions is no ':' and no NULL characters and 255 UTF16 char limit.\n        let invalidCases = [\n            \"\",\n            \":\",\n            \"\\0\",\n            \"Hell\\0 World!\",\n            \"export:2024-04-12.txt\",\n            // 256 characters (1 too long)\n            String((0..<256).map({ _ in \"abcd\".randomElement() ?? Character(\"\") }))\n        ]\n\n        for invalidCase in invalidCases {\n            XCTAssertFalse(invalidCase.isValidFilename, \"Detected valid case \\\"\\(invalidCase)\\\", should be invalid.\")\n        }\n    }\n\n    // MARK: - STRING + ESCAPED\n\n    func testEscapeQuotes() {\n        let string = #\"this/is/\"a path/Hello \"world\"#\n        XCTAssertEqual(string.escapedQuotes(), #\"this/is/\\\"a path/Hello \\\"world\"#)\n    }\n\n    func testEscapeQuotesForAlreadyEscapedString() {\n        let string = #\"this/is/\"a path/Hello \\\"world\"#\n        XCTAssertEqual(string.escapedQuotes(), #\"this/is/\\\"a path/Hello \\\"world\"#)\n    }\n\n    func testEscapedDirectory() {\n        let path = #\"/Hello World/ With Spaces/ And \" Characters \"#\n        XCTAssertEqual(path.escapedDirectory(), #\"\"/Hello World/ With Spaces/ And \\\" Characters \"\"#)\n    }\n\n    // MARK: - URL + Contains\n\n    func testURLContainsSubPath() {\n        XCTAssertTrue(URL(filePath: \"/Users/Bob/Desktop\").containsSubPath(URL(filePath: \"/Users/Bob/Desktop/file.txt\")))\n        XCTAssertFalse(URL(filePath: \"/Users/Bob/Desktop\").containsSubPath(URL(filePath: \"/Users/Bob/Desktop/\")))\n        XCTAssertFalse(URL(filePath: \"/Users/Bob/Desktop\").containsSubPath(URL(filePath: \"/Users/Bob/\")))\n        XCTAssertTrue(URL(filePath: \"/Users/Bob/Desktop\").containsSubPath(URL(filePath: \"/Users/Bob/Desktop/Folder\")))\n    }\n\n    func testURLSharedComponentsCount() {\n        // URL Treats the leading `/` as a component, so these all appear to have + 1 but are correct.\n        XCTAssertEqual(\n            URL(filePath: \"/Users/Bob/Desktop\").sharedComponents(URL(filePath: \"/Users/Bob/Desktop/file.txt\")),\n            4\n        )\n        XCTAssertEqual(\n            URL(filePath: \"/Users/Bob/Desktop\").sharedComponents(URL(filePath: \"/Users/Bob/Desktop/\")),\n            4\n        )\n        XCTAssertEqual(\n            URL(filePath: \"/Users/Bob/Desktop\").sharedComponents(URL(filePath: \"/Users/Bob/\")),\n            3\n        )\n        XCTAssertEqual(\n            URL(filePath: \"/Users/Bob/Desktop\").sharedComponents(URL(filePath: \"/Users/Bob/Desktop/Folder\")),\n            4\n        )\n\n        XCTAssertEqual(\n            URL(filePath: \"/Users/Bob/Desktop\").sharedComponents(URL(filePath: \"/Some Other/ Path \")),\n            1\n        )\n    }\n}\n"
  },
  {
    "path": "CodeEditTests/Utils/waitForExpectation.swift",
    "content": "//\n//  waitForExpectation.swift\n//  CodeEditTests\n//\n//  Created by Khan Winter on 7/15/25.\n//\n\nfunc waitForExpectation(\n    timeout: ContinuousClock.Duration = .seconds(2.0),\n    _ expectation: () throws -> Bool,\n    onTimeout: () throws -> Void\n) async rethrows {\n    let start = ContinuousClock.now\n    while .now - start < timeout {\n        if try expectation() {\n            return\n        } else {\n            await Task.yield()\n        }\n    }\n\n    try onTimeout()\n}\n"
  },
  {
    "path": "CodeEditTests/Utils/withTempDir.swift",
    "content": "//\n//  withTempDir.swift\n//  CodeEditTests\n//\n//  Created by Khan Winter on 7/3/25.\n//\n\nimport Foundation\nimport Testing\n\nfunc withTempDir(_ test: (URL) async throws -> Void) async throws {\n    guard let currentTest = Test.current else {\n        #expect(Bool(false))\n        return\n    }\n    let tempDirURL = try createAndClearDir(file: currentTest.sourceLocation.fileID + currentTest.name)\n    do {\n        try await test(tempDirURL)\n    } catch {\n        try clearDir(tempDirURL)\n        throw error\n    }\n    try clearDir(tempDirURL)\n}\n\nfunc withTempDir(_ test: (URL) throws -> Void) throws {\n    guard let currentTest = Test.current else {\n        #expect(Bool(false))\n        return\n    }\n    let tempDirURL = try createAndClearDir(file: currentTest.sourceLocation.fileID + currentTest.name)\n    do {\n        try test(tempDirURL)\n    } catch {\n        try clearDir(tempDirURL)\n        throw error\n    }\n    try clearDir(tempDirURL)\n}\n\nprivate func createAndClearDir(file: String) throws -> URL {\n    let file = file.components(separatedBy: CharacterSet(charactersIn: \"/:?%*|\\\"<>\")).joined()\n    let tempDirURL = FileManager.default.temporaryDirectory\n        .appending(path: \"CodeEditTestDirectory\" + file, directoryHint: .isDirectory)\n\n    // If it exists, delete it before the test\n    try clearDir(tempDirURL)\n\n    try FileManager.default.createDirectory(at: tempDirURL, withIntermediateDirectories: true)\n\n    return tempDirURL\n}\n\nprivate func clearDir(_ url: URL) throws {\n    if FileManager.default.fileExists(atPath: url.absoluteURL.path(percentEncoded: false)) {\n        try FileManager.default.removeItem(at: url)\n    }\n}\n"
  },
  {
    "path": "CodeEditUI/src/Preferences/ViewOffsetPreferenceKey.swift",
    "content": "import SwiftUI\n\n/// Tracks scroll offset in scrollable views\npublic struct ViewOffsetPreferenceKey: PreferenceKey {\n    public typealias Value = CGFloat\n    public static var defaultValue = CGFloat.zero\n    public static func reduce(value: inout Value, nextValue: () -> Value) {\n        value += nextValue()\n    }\n}\n"
  },
  {
    "path": "CodeEditUITests/App.swift",
    "content": "//\n//  App.swift\n//  CodeEditUITests\n//\n//  Created by Khan Winter on 7/21/24.\n//\n\nimport XCTest\n\nenum App {\n    static func launchWithCodeEditWorkspace() -> XCUIApplication {\n        let application = XCUIApplication()\n        application.launchArguments = [\"-ApplePersistenceIgnoreState\", \"YES\", \"--open\", projectPath()]\n        application.launch()\n        return application\n    }\n\n    // Launches CodeEdit in a new directory and returns the directory path.\n    static func launchWithTempDir() throws -> (XCUIApplication, String) {\n        let tempDirURL = try tempProjectPath()\n        let application = XCUIApplication()\n        application.launchArguments = [\"-ApplePersistenceIgnoreState\", \"YES\", \"--open\", tempDirURL]\n        application.launch()\n        return (application, tempDirURL)\n    }\n\n    static func launch() -> XCUIApplication {\n        let application = XCUIApplication()\n        application.launchArguments = [\"-ApplePersistenceIgnoreState\", \"YES\"]\n        application.launch()\n        return application\n    }\n}\n"
  },
  {
    "path": "CodeEditUITests/Extensions/XCUITest+waitForNonExistence.swift",
    "content": "//\n//  XCUITest+waitForNonExistence.swift\n//  CodeEditUITests\n//\n//  Created by Khan Winter on 1/7/25.\n//\n\nimport XCTest\n\n// Backport to Xcode 15, this exists in Xcode 16.\n\nextension XCUIElement {\n    /// Waits the specified amount of time for the element’s `exists` property to become `false`.\n    /// - Parameter timeout: The amount of time to wait.\n    /// - Returns: `false` if the timeout expires without the element coming out of existence.\n    func waitForNonExistence(timeout: TimeInterval) -> Bool {\n        let predicate = NSPredicate(format: \"exists == false\")\n        switch XCTWaiter.wait(for: [XCTNSPredicateExpectation(predicate: predicate, object: self)], timeout: timeout) {\n        case .completed:\n            return true\n        default:\n            return false\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEditUITests/Features/ActivityViewer/Tasks/TasksMenuUITests.swift",
    "content": "//\n//  ActivityViewerTasksMenuTests.swift\n//  CodeEditUITests\n//\n//  Created by Khan Winter on 1/3/25.\n//\n\nimport XCTest\n\nfinal class ActivityViewerTasksMenuTests: XCTestCase {\n    // After all tests in this group\n    override static func tearDown() {\n        do {\n            try cleanUpTempProjectPaths()\n        } catch {\n            print(\"Failed to clean up test temp directories.\")\n            print(error)\n        }\n    }\n\n    var app: XCUIApplication!\n    var window: XCUIElement!\n\n    @MainActor\n    override func setUp() async throws {\n        (app, _) = try App.launchWithTempDir()\n        window = Query.getWindow(app)\n        XCTAssertTrue(window.exists, \"Window not found\")\n    }\n\n    func testTaskMenu() {\n        let viewer = window.groups[\"Activity Viewer\"]\n        XCTAssertNotNil(viewer, \"No Activity Viewer\")\n\n        let taskDropdown = viewer.buttons[\"Active Task\"]\n        XCTAssertTrue(taskDropdown.waitForExistence(timeout: 2.0), \"No Task Dropdown\")\n        XCTAssertEqual(taskDropdown.value as? String, \"Create Tasks\", \"Incorrect empty tasks label\")\n\n        taskDropdown.click()\n        XCTAssertGreaterThan(app.popovers.count, 0, \"Popover didn't show up\")\n    }\n\n    func testNewTask() {\n        let viewer = window.groups[\"Activity Viewer\"]\n        let taskDropdown = viewer.buttons[\"Active Task\"]\n        taskDropdown.click()\n        let popover = app.popovers.firstMatch\n        XCTAssertTrue(popover.exists, \"Popover did not appear on click\")\n\n        let addTaskListOption = popover.buttons[\"Add Task...\"]\n        XCTAssertTrue(addTaskListOption.exists, \"No add task option in dropdown\")\n        addTaskListOption.click()\n\n        let workspaceSettingsWindow = window.sheets[\"Workspace Settings\"]\n        XCTAssertTrue(workspaceSettingsWindow.waitForExistence(timeout: 1.0), \"Workspace settings did not appear\")\n\n        let addTaskButton = workspaceSettingsWindow.buttons[\"Add Task...\"]\n        XCTAssertTrue(addTaskButton.exists, \"No add task button\")\n        addTaskButton.click()\n\n        // Enter in task information\n        let newSheet = workspaceSettingsWindow.sheets.firstMatch\n        XCTAssertTrue(newSheet.waitForExistence(timeout: 1.0), \"New task sheet did not appear\")\n        let taskName = newSheet.textFields[\"Task Name\"]\n        XCTAssertTrue(taskName.exists)\n        taskName.click()\n        taskName.typeText(\"New Test Task\")\n        XCTAssertEqual(taskName.value as? String, \"New Test Task\", \"Name did not enter in\")\n\n        let taskCommand = newSheet.textFields[\"Task Command\"]\n        XCTAssertTrue(taskCommand.exists)\n        taskCommand.click()\n        taskCommand.typeText(\"echo \\\"Hello World\\\"\")\n        XCTAssertEqual(taskCommand.value as? String, \"echo \\\"Hello World\\\"\", \"Command did not enter in\")\n\n        let saveButton = newSheet.buttons[\"Save\"]\n        XCTAssertTrue(saveButton.exists)\n        saveButton.click()\n\n        workspaceSettingsWindow.buttons[\"Done\"].click()\n        XCTAssertFalse(\n            workspaceSettingsWindow.waitForNonExistence(timeout: 1.0),\n            \"Workspace Settings should have dismissed\"\n        )\n\n        // Ensure the new task was added as an option\n        XCTAssertEqual(taskDropdown.value as? String, \"New Test Task\")\n        taskDropdown.click()\n        XCTAssertTrue(popover.buttons[\"New Test Task\"].exists, \"New task was not added to the task list.\")\n    }\n}\n"
  },
  {
    "path": "CodeEditUITests/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorFileManagementUITests.swift",
    "content": "//\n//  ProjectNavigatorFileManagementUITests.swift\n//  CodeEditUITests\n//\n//  Created by Khan Winter on 1/15/25.\n//\n\nimport XCTest\n\nfinal class ProjectNavigatorFileManagementUITests: XCTestCase {\n\n    var app: XCUIApplication!\n    var window: XCUIElement!\n    var navigator: XCUIElement!\n    var path: String!\n\n    override func setUp() async throws {\n        // MainActor required for async compatibility which is required to make this method throwing\n        try await MainActor.run {\n            (app, path) = try App.launchWithTempDir()\n\n            window = Query.getWindow(app)\n            XCTAssertTrue(window.exists, \"Window not found\")\n            window.toolbars.firstMatch.click()\n\n            navigator = Query.Window.getProjectNavigator(window)\n            XCTAssertTrue(navigator.exists, \"Navigator not found\")\n            XCTAssertEqual(Query.Navigator.getRows(navigator).count, 1, \"Found more than just the root file.\")\n        }\n    }\n\n    func testNewFilesAppear() throws {\n        // Create a few files, one in the base path and one inside a new folder. They should all appear in the navigator\n\n        guard FileManager.default.createFile(atPath: path.appending(\"/newFile.txt\"), contents: nil) else {\n            XCTFail(\"Failed to create test file\")\n            return\n        }\n\n        try FileManager.default.createDirectory(\n            atPath: path.appending(\"/New Folder\"),\n            withIntermediateDirectories: true\n        )\n\n        guard FileManager.default.createFile(\n            atPath: path.appending(\"/New Folder/My New JS File.jsx\"),\n            contents: nil\n        ) else {\n            XCTFail(\"Failed to create second test file\")\n            return\n        }\n\n        guard Query.Navigator.getProjectNavigatorRow(\n            fileTitle: \"newFile.txt\",\n            navigator\n        ).waitForExistence(timeout: 2.0) else {\n            XCTFail(\"newFile.txt did not appear\")\n            return\n        }\n\n        guard Query.Navigator.getProjectNavigatorRow(\n            fileTitle: \"New Folder\",\n            navigator\n        ).waitForExistence(timeout: 2.0) else {\n            XCTFail(\"New Folder did not appear\")\n            return\n        }\n\n        let folderRow = Query.Navigator.getProjectNavigatorRow(fileTitle: \"New Folder\", navigator)\n        folderRow.disclosureTriangles.element.click()\n\n        guard Query.Navigator.getProjectNavigatorRow(\n            fileTitle: \"My New JS File.jsx\",\n            navigator\n        ).waitForExistence(timeout: 2.0) else {\n            XCTFail(\"New file inside the folder did not appear when folder was opened.\")\n            return\n        }\n    }\n\n    func testCreateNewFiles() throws {\n        // Add a few files with the navigator button\n        for idx in 0..<5 {\n            let addButton = window.popUpButtons[\"addButton\"]\n            addButton.click()\n            let addMenu = addButton.menus.firstMatch\n            addMenu.menuItems[\"Add File\"].click()\n\n            let selectedRows = Query.Navigator.getSelectedRows(navigator)\n            guard selectedRows.firstMatch.waitForExistence(timeout: 0.5) else {\n                XCTFail(\"No new selected rows appeared\")\n                return\n            }\n\n            let title = idx > 0 ? \"untitled\\(idx)\" : \"untitled\"\n\n            let newFileRow = selectedRows.firstMatch\n            XCTAssertEqual(newFileRow.descendants(matching: .textField).firstMatch.value as? String, title)\n\n            let tabBar = Query.Window.getTabBar(window)\n            XCTAssertTrue(tabBar.exists)\n            let readmeTab = Query.TabBar.getTab(labeled: title, tabBar)\n            XCTAssertTrue(readmeTab.exists)\n\n            let newFileEditor = Query.Window.getFirstEditor(window)\n            XCTAssertTrue(newFileEditor.exists)\n            XCTAssertNotNil(newFileEditor.value as? String)\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEditUITests/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorUITests.swift",
    "content": "//\n//  ProjectNavigatorUITests.swift\n//  CodeEditTests\n//\n//  Created by Khan Winter on 7/9/24.\n//\n\nimport XCTest\n\nfinal class ProjectNavigatorUITests: XCTestCase {\n\n    var application: XCUIApplication!\n\n    override func setUp() {\n        application = App.launchWithCodeEditWorkspace()\n    }\n\n    func testNavigatorOpenFilesAndFolder() {\n        let window = Query.getWindow(application)\n        XCTAssertTrue(window.exists, \"Window not found\")\n        // Focus the window\n        window.toolbars.firstMatch.click()\n\n        // Get the navigator\n        let navigator = Query.Window.getProjectNavigator(window)\n        XCTAssertTrue(navigator.exists, \"Navigator not found\")\n\n        // Open the README.md\n        let readmeRow = Query.Navigator.getProjectNavigatorRow(fileTitle: \"README.md\", navigator)\n        XCTAssertFalse(Query.Navigator.rowContainsDisclosureIndicator(readmeRow), \"File has disclosure indicator\")\n        readmeRow.click()\n\n        let tabBar = Query.Window.getTabBar(window)\n        XCTAssertTrue(tabBar.exists)\n        let readmeTab = Query.TabBar.getTab(labeled: \"README.md\", tabBar)\n        XCTAssertTrue(readmeTab.exists)\n\n        let readmeEditor = Query.Window.getFirstEditor(window)\n        XCTAssertTrue(readmeEditor.exists)\n        XCTAssertNotNil(readmeEditor.value as? String)\n\n        let rowCount = navigator.descendants(matching: .outlineRow).count\n\n        // Open a folder\n        let codeEditFolderRow = Query.Navigator.getProjectNavigatorRow(fileTitle: \"CodeEdit\", index: 1, navigator)\n        XCTAssertTrue(codeEditFolderRow.exists)\n        XCTAssertTrue(\n            Query.Navigator.rowContainsDisclosureIndicator(codeEditFolderRow),\n            \"Folder doesn't have disclosure indicator\"\n        )\n        let folderDisclosureIndicator = Query.Navigator.disclosureIndicatorForRow(codeEditFolderRow)\n        folderDisclosureIndicator.click()\n\n        let newRowCount = navigator.descendants(matching: .outlineRow).count\n        XCTAssertTrue(newRowCount > rowCount, \"No new rows were loaded after opening the folder\")\n\n        folderDisclosureIndicator.click()\n        let finalRowCount = navigator.descendants(matching: .outlineRow).count\n        XCTAssertTrue(newRowCount > finalRowCount, \"Rows were not hidden after closing a folder\")\n        XCTAssertEqual(rowCount, finalRowCount, \"Different Number of rows loaded\")\n    }\n}\n"
  },
  {
    "path": "CodeEditUITests/Features/UtilityArea/TerminalUtility/TerminalUtilityUITests.swift",
    "content": "//\n//  TerminalUtilityUITests.swift\n//  CodeEditUITests\n//\n//  Created by Khan Winter on 8/8/25.\n//\n\nimport XCTest\n\nfinal class TerminalUtilityUITests: XCTestCase {\n    var app: XCUIApplication!\n    var window: XCUIElement!\n    var utilityArea: XCUIElement!\n    var path: String!\n\n    override func setUp() async throws {\n        // MainActor required for async compatibility which is required to make this method throwing\n        try await MainActor.run {\n            (app, path) = try App.launchWithTempDir()\n\n            window = Query.getWindow(app)\n            XCTAssertTrue(window.exists, \"Window not found\")\n            window.toolbars.firstMatch.click()\n\n            utilityArea = Query.Window.getUtilityArea(window)\n            XCTAssertTrue(utilityArea.exists, \"Utility Area not found\")\n        }\n    }\n\n    func testTerminalsInputData() throws {\n        let terminal = utilityArea.textViews[\"Terminal Emulator\"]\n        XCTAssertTrue(terminal.exists)\n        terminal.click()\n        terminal.typeText(\"echo hello world\")\n        terminal.typeKey(.enter, modifierFlags: [])\n\n        let value = try XCTUnwrap(terminal.value as? String)\n        XCTAssertEqual(value.components(separatedBy: \"hello world\").count - 1, 2)\n    }\n\n    func testTerminalsKeepData() throws {\n        var terminal = utilityArea.textViews[\"Terminal Emulator\"]\n        XCTAssertTrue(terminal.exists)\n        terminal.click()\n        terminal.typeText(\"echo hello world\")\n        terminal.typeKey(.enter, modifierFlags: [])\n\n        let terminals = utilityArea.descendants(matching: .any).matching(identifier: \"terminalsList\").element\n        XCTAssertTrue(terminals.exists)\n        terminals.click()\n\n        let terminalRow = terminals.cells.firstMatch\n        XCTAssertTrue(terminalRow.exists)\n        terminalRow.click()\n\n        terminal = utilityArea.textViews[\"Terminal Emulator\"]\n        XCTAssertTrue(terminal.exists)\n\n        let finalValue = try XCTUnwrap(terminal.value as? String)\n        XCTAssertEqual(finalValue.components(separatedBy: \"hello world\").count - 1, 2)\n    }\n}\n"
  },
  {
    "path": "CodeEditUITests/Other Tests/HideInterfaceTests.swift",
    "content": "//\n//  HiderInterfaceTests.swift\n//  CodeEditUITests\n//\n//  Created by Simon Kudsk on 14/05/2025.\n//\n\nimport XCTest\nfinal class HideInterfaceUITests: XCTestCase {\n\n    // MARK: – Setup\n    private var app: XCUIApplication!\n    private var path: String!\n\n    override func setUp() async throws {\n        try await MainActor.run {\n            (app, path) = try App.launchWithTempDir()\n        }\n    }\n\n    /// List of the  panels to test with\n    private let allPanels: () -> [String] = {\n        [\"Navigator\", \"Inspector\", \"Utility Area\", \"Toolbar\"]\n    }\n\n    // MARK: – Tests\n\n    /// Test 1: Ensure each panel can show and hide individually.\n    func testPanelsShowAndHideIndividually() {\n        let viewMenu = app.menuBars.menuBarItems[\"View\"]\n        for panel in allPanels() {\n            // Show panel\n            let showItem = \"Show \\(panel)\"\n            if viewMenu.menuItems[showItem].exists {\n                viewMenu.menuItems[showItem].click()\n            }\n\n            // Verify panel is visible\n            viewMenu.click()\n            XCTAssertTrue(viewMenu.menuItems[\"Hide \\(panel)\"].exists, \"\\(panel) should be visible after show\")\n\n            // Hide panel and verify it being hidden\n            viewMenu.menuItems[(\"Hide \\(panel)\")].click()\n            viewMenu.click()\n            XCTAssertTrue(viewMenu.menuItems[\"Show \\(panel)\"].exists, \"\\(panel) should be hidden after hide\")\n        }\n    }\n\n    /// Test 2: Hide interface hides all panels.\n    func testHideInterfaceHidesAllPanels() {\n        let viewMenu = app.menuBars.menuBarItems[\"View\"]\n        // Ensure all panels are shown\n        for panel in allPanels() {\n            let showItem = \"Show \\(panel)\"\n            if viewMenu.menuItems[showItem].exists {\n                viewMenu.menuItems[showItem].click()\n            }\n        }\n\n        // Hide interface\n        viewMenu.menuItems[(\"Hide Interface\")].click()\n\n        // Verify all panels are hidden\n        viewMenu.click()\n        for panel in allPanels() {\n            XCTAssertTrue(viewMenu.menuItems[\"Show \\(panel)\"].exists, \"\\(panel) should be hidden\")\n        }\n    }\n\n    /// Test 3: Show interface shows all panels when none are visible.\n    func testShowInterfaceShowsAllWhenNoneVisible() {\n        let viewMenu = app.menuBars.menuBarItems[\"View\"]\n        // Ensure all panels are hidden\n        for panel in allPanels() {\n            let hideItem = \"Hide \\(panel)\"\n            if viewMenu.menuItems[hideItem].exists {\n                viewMenu.menuItems[hideItem].click()\n            }\n        }\n\n        // Verify button says Show Interface\n        viewMenu.click()\n        XCTAssertTrue(viewMenu.menuItems[\"Show Interface\"].exists, \"Interface button should say Show Interface\")\n\n        // Show interface without waiting\n        viewMenu.menuItems[(\"Show Interface\")].click()\n\n        // Verify all panels are shown\n        viewMenu.click()\n        for panel in allPanels() {\n            XCTAssertTrue(\n                viewMenu.menuItems[\"Hide \\(panel)\"].exists,\n                \"\\(panel) should be visible after showing interface\"\n            )\n        }\n    }\n\n    /// Test 4: Show interface restores previous panel state.\n    func testShowInterfaceRestoresPreviousState() {\n        let viewMenu = app.menuBars.menuBarItems[\"View\"]\n        let initialOpen = [\"Navigator\", \"Toolbar\"]\n\n        // Set initial state\n        for panel in allPanels() {\n            let item = initialOpen.contains(panel) ? \"Show \\(panel)\" : \"Hide \\(panel)\"\n            if viewMenu.menuItems[item].exists {\n                viewMenu.menuItems[item].click()\n            }\n        }\n\n        // Hide then show interface\n        viewMenu.menuItems[(\"Hide Interface\")].click()\n        viewMenu.menuItems[(\"Show Interface\")].click()\n\n        // Verify only initial panels are shown\n        viewMenu.click()\n        for panel in allPanels() {\n            let shouldBeVisible = initialOpen.contains(panel)\n            XCTAssertEqual(viewMenu.menuItems[\"Hide \\(panel)\"].exists, shouldBeVisible, \"\\(panel) visibility mismatch\")\n        }\n    }\n\n    /// Test 5: Individual toggles after hide update the interface button.\n    func testIndividualTogglesUpdateInterfaceButton() {\n        let viewMenu = app.menuBars.menuBarItems[\"View\"]\n        let initialOpen = [\"Navigator\", \"Toolbar\"]\n\n        // Set initial visibility\n        for panel in allPanels() {\n            let item = initialOpen.contains(panel) ? \"Show \\(panel)\" : \"Hide \\(panel)\"\n            if viewMenu.menuItems[item].exists {\n                viewMenu.menuItems[item].click()\n            }\n        }\n\n        // Hide interface\n        viewMenu.menuItems[(\"Hide Interface\")].click()\n\n        // Individually enable initial panels\n        for panel in initialOpen {\n            viewMenu.menuItems[(\"Show \\(panel)\")].click()\n        }\n\n        // Verify interface button resets to Hide Interface\n        viewMenu.click()\n        XCTAssertTrue(\n            viewMenu.menuItems[\"Hide Interface\"].exists,\n            \"Interface should say hide interface when all previous panels are enabled again\"\n        )\n    }\n\n    /// Test 6: Partial show after hide restores correct panels.\n    func testPartialShowAfterHideRestoresCorrectPanels() {\n        let viewMenu = app.menuBars.menuBarItems[\"View\"]\n        let initialOpen = [\"Navigator\", \"Toolbar\"]\n\n        // Set initial visibility\n        for panel in allPanels() {\n            let item = initialOpen.contains(panel) ? \"Show \\(panel)\" : \"Hide \\(panel)\"\n            if viewMenu.menuItems[item].exists {\n                viewMenu.menuItems[item].click()\n            }\n        }\n\n        // Hide interface\n        viewMenu.menuItems[(\"Hide Interface\")].click()\n\n        // Individually enable navigator and inspector\n        for panel in [\"Navigator\", \"Inspector\"] {\n            viewMenu.menuItems[(\"Show \\(panel)\")].click()\n        }\n        // Show interface\n        viewMenu.menuItems[(\"Show Interface\")].click()\n\n        // Verify correct panels are shown\n        viewMenu.click()\n        for panel in [\"Navigator\", \"Inspector\", \"Toolbar\"] {\n            XCTAssertTrue(viewMenu.menuItems[\"Hide \\(panel)\"].exists, \"\\(panel) should be visible\")\n        }\n\n        // Utility Area should remain hidden\n        XCTAssertTrue(viewMenu.menuItems[\"Show Utility Area\"].exists, \"Utility Area should be hidden\")\n    }\n}\n"
  },
  {
    "path": "CodeEditUITests/Other Tests/WindowCloseCommandTests.swift",
    "content": "//\n//  WindowCloseCommandTests.swift\n//  CodeEditUITests\n//\n//  Created by Khan Winter on 7/21/24.\n//\n\nimport XCTest\n\n/// Tests for window closing commands.\n/// - Note: feel free to add on in the future and change this test name.\nfinal class WindowCloseCommandTests: XCTestCase {\n    // swiftier api (expectation(that: , on:, willEqual:) doesn't work :(\n    let notExistsPredicate = NSPredicate(format: \"exists == false\")\n\n    var application: XCUIApplication!\n\n    func testWorkspaceWindowCloses() {\n        application = App.launchWithCodeEditWorkspace()\n        let window = Query.getWindow(application)\n        XCTAssertTrue(window.waitForExistence(timeout: 5.0), \"Workspace didn't open\")\n        window.toolbars.firstMatch.click()\n\n        let expectation = expectation(for: notExistsPredicate, evaluatedWith: window)\n        application.typeKey(\"w\", modifierFlags: .command)\n        wait(for: [expectation], timeout: 5.0)\n    }\n\n    func testWorkspaceTabCloses() {\n        application = App.launchWithCodeEditWorkspace()\n        let window = Query.getWindow(application)\n        XCTAssertTrue(window.waitForExistence(timeout: 5.0), \"Workspace didn't open\")\n\n        window.toolbars.firstMatch.click()\n\n        let navigator = Query.Window.getProjectNavigator(window)\n        let readmeRow = Query.Navigator.getProjectNavigatorRow(fileTitle: \"README.md\", navigator)\n        XCTAssertTrue(navigator.exists)\n        XCTAssertTrue(readmeRow.exists)\n        readmeRow.click()\n\n        let tabBar = Query.Window.getTabBar(window)\n        XCTAssertTrue(tabBar.exists)\n        let readmeTab = Query.TabBar.getTab(labeled: \"README.md\", tabBar)\n        XCTAssertTrue(readmeTab.exists)\n        XCTAssertEqual(tabBar.descendants(matching: .group).count, 1)\n\n        let tabCloseExpectation = expectation(for: notExistsPredicate, evaluatedWith: readmeTab)\n        application.typeKey(\"w\", modifierFlags: .command)\n        wait(for: [tabCloseExpectation], timeout: 5.0)\n        XCTAssertEqual(tabBar.descendants(matching: .group).count, 0)\n\n        let windowCloseExpectation = expectation(for: notExistsPredicate, evaluatedWith: window)\n        application.typeKey(\"w\", modifierFlags: .command)\n        wait(for: [windowCloseExpectation], timeout: 5.0)\n    }\n\n    func testWorkspaceClosesWithTabStillOpen() {\n        application = App.launchWithCodeEditWorkspace()\n        let window = Query.getWindow(application)\n        XCTAssertTrue(window.waitForExistence(timeout: 5.0), \"Workspace didn't open\")\n\n        window.toolbars.firstMatch.click()\n\n        let navigator = Query.Window.getProjectNavigator(window)\n        let readmeRow = Query.Navigator.getProjectNavigatorRow(fileTitle: \"README.md\", navigator)\n        XCTAssertTrue(navigator.exists)\n        XCTAssertTrue(readmeRow.exists)\n        readmeRow.click()\n\n        let tabBar = Query.Window.getTabBar(window)\n        XCTAssertTrue(tabBar.exists)\n        let readmeTab = Query.TabBar.getTab(labeled: \"README.md\", tabBar)\n        XCTAssertTrue(readmeTab.exists)\n        XCTAssertEqual(tabBar.descendants(matching: .group).count, 1)\n\n        let windowCloseExpectation = expectation(for: notExistsPredicate, evaluatedWith: window)\n        application.typeKey(\"w\", modifierFlags: [.shift, .command])\n        wait(for: [windowCloseExpectation], timeout: 5.0)\n    }\n\n    func testSettingsWindowCloses() {\n        application = App.launch()\n        let window = Query.getSettingsWindow(application)\n        application.typeKey(\",\", modifierFlags: .command)\n        XCTAssertTrue(window.waitForExistence(timeout: 5.0), \"Settings didn't open\")\n\n        let expectation = expectation(for: notExistsPredicate, evaluatedWith: window)\n        application.typeKey(\"w\", modifierFlags: .command)\n        wait(for: [expectation], timeout: 5.0)\n    }\n\n    func testWelcomeWindowCloses() {\n        application = App.launch()\n        let window = Query.getWelcomeWindow(application)\n        application.typeKey(\"1\", modifierFlags: [.shift, .command])\n        XCTAssertTrue(window.waitForExistence(timeout: 5.0), \"Welcome didn't open\")\n\n        let expectation = expectation(for: notExistsPredicate, evaluatedWith: window)\n        application.typeKey(\"w\", modifierFlags: .command)\n        wait(for: [expectation], timeout: 5.0)\n    }\n\n    func testAboutWindowCloses() {\n        application = App.launch()\n        let window = Query.getAboutWindow(application)\n        application.typeKey(\"2\", modifierFlags: [.shift, .command])\n        XCTAssertTrue(window.waitForExistence(timeout: 5.0), \"About didn't open\")\n\n        let expectation = expectation(for: notExistsPredicate, evaluatedWith: window)\n        application.typeKey(\"w\", modifierFlags: .command)\n        wait(for: [expectation], timeout: 5.0)\n    }\n\n}\n"
  },
  {
    "path": "CodeEditUITests/ProjectPath.swift",
    "content": "//\n//  ProjectPath.swift\n//  CodeEditUITests\n//\n//  Created by Khan Winter on 7/10/24.\n//\n\nimport Foundation\n\nfunc projectPath() -> String {\n    return String(\n        URL(fileURLWithPath: #filePath)\n            .pathComponents\n            .prefix(while: { $0 != \"CodeEditUITests\" })\n            .joined(separator: \"/\")\n            .dropFirst()\n    )\n}\n\nprivate var tempProjectPathIds = Set<String>()\n\nprivate func makeTempID() -> String {\n    let id = String((0..<10).map { _ in \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-\".randomElement()! })\n    if tempProjectPathIds.contains(id) {\n        return makeTempID()\n    }\n    tempProjectPathIds.insert(id)\n    return id\n}\n\nfunc tempProjectPath() throws -> String {\n    let baseDir = FileManager.default.temporaryDirectory.appending(path: \"CodeEditUITests\")\n    let id = makeTempID()\n    let path = baseDir.appending(path: id)\n    try FileManager.default.createDirectory(at: path, withIntermediateDirectories: true)\n    return path.path(percentEncoded: false)\n}\n\nfunc cleanUpTempProjectPaths() throws {\n    let baseDir = FileManager.default.temporaryDirectory.appending(path: \"CodeEditUITests\")\n    try FileManager.default.removeItem(at: baseDir)\n    tempProjectPathIds.removeAll()\n}\n"
  },
  {
    "path": "CodeEditUITests/Query.swift",
    "content": "//\n//  Query.swift\n//  CodeEditUITests\n//\n//  Created by Khan Winter on 7/10/24.\n//\n\nimport XCTest\n\n/// Query helpers for querying for specific UI elements. Organized by category in the app.\n/// Queries should not evaluate if an element exists. This allows for tests to expect an element to exist,\n/// perform an action, and then wait for that element to exist.\nenum Query {\n    static func getWindow(_ application: XCUIApplication, named: String? = nil) -> XCUIElement {\n        if let named {\n            return application.windows[named]\n        } else {\n            return application.windows.element(matching: .window, identifier: \"workspace\")\n        }\n    }\n\n    static func getSettingsWindow(_ application: XCUIApplication) -> XCUIElement {\n        return application.windows.element(matching: .window, identifier: \"settings\")\n    }\n\n    static func getWelcomeWindow(_ application: XCUIApplication) -> XCUIElement {\n        return application.windows.element(matching: .window, identifier: \"welcome\")\n    }\n\n    static func getAboutWindow(_ application: XCUIApplication) -> XCUIElement {\n        return application.windows.element(matching: .window, identifier: \"about\")\n    }\n\n    enum Window {\n        static func getProjectNavigator(_ window: XCUIElement) -> XCUIElement {\n            return window.descendants(matching: .any).matching(identifier: \"ProjectNavigator\").element\n        }\n\n        static func getTabBar(_ window: XCUIElement) -> XCUIElement {\n            return window.descendants(matching: .any).matching(identifier: \"TabBar\").element\n        }\n\n        static func getUtilityArea(_ window: XCUIElement) -> XCUIElement {\n            return window.descendants(matching: .any).matching(identifier: \"UtilityArea\").element\n        }\n\n        static func getFirstEditor(_ window: XCUIElement) -> XCUIElement {\n            return window.descendants(matching: .any)\n                .matching(NSPredicate(format: \"label CONTAINS[c] 'Text Editor'\"))\n                .firstMatch\n        }\n    }\n\n    enum Navigator {\n        static func getRows(_ navigator: XCUIElement) -> XCUIElementQuery {\n            navigator.descendants(matching: .outlineRow)\n        }\n\n        static func getSelectedRows(_ navigator: XCUIElement) -> XCUIElementQuery {\n            getRows(navigator).matching(NSPredicate(format: \"selected = true\"))\n        }\n\n        static func getProjectNavigatorRow(fileTitle: String, index: Int = 0, _ navigator: XCUIElement) -> XCUIElement {\n            return getRows(navigator)\n                .containing(.textField, identifier: \"ProjectNavigatorTableViewCell-\\(fileTitle)\")\n                .element(boundBy: index)\n        }\n\n        static func disclosureIndicatorForRow(_ row: XCUIElement) -> XCUIElement {\n            row.descendants(matching: .disclosureTriangle).element\n        }\n\n        static func rowContainsDisclosureIndicator(_ row: XCUIElement) -> Bool {\n            disclosureIndicatorForRow(row).exists\n        }\n    }\n\n    enum TabBar {\n        static func getTab(labeled title: String, _ tabBar: XCUIElement) -> XCUIElement {\n            tabBar.descendants(matching: .group).containing(NSPredicate(format: \"value = %@\", title)).firstMatch\n        }\n    }\n}\n"
  },
  {
    "path": "CodeEditUITests/UI TESTING.md",
    "content": "# UI Testing in CodeEdit\n\nCodeEdit uses XCUITests for automating tests that require user interaction. Ideally, we have UI tests for every UI \ncomponent in CodeEdit, but right now (as of Jan, 2025) we have fewer tests than we'd like.\n\n## Test Application Setup\n\nTo test workspaces with real files, launch the application with the `App` enum. To create a temporary test directory,\nuse the `App.launchWithTempDir()` method. This will create a random directory in the temporary directory and return\nthe created path. In tests you can add files to that directory and it will be cleaned up when the tests finish.\n\nThere is a `App.launchWithCodeEditWorkspace` method, but please try not to use it. It exists for compatibility with a\nfew existing tests and would be a pain to replace. It's more likely to be flaky, and can't test things like file\nmodification, creation, or anything besides clicking around the workspace or you risk modifying the very project\nthat's being tested!\n\n## Query Extensions\n\nFor common, long, queries, add static methods to the `Query` enum. This enum should be used to help clarify tests\nwithout having to read long XCUI queries. For instance\n```swift\nlet window = application.windows.element(matching: .window, identifier: \"workspace\")\nlet navigator = window.descendants(matching: .any).matching(identifier: \"ProjectNavigator\").element\nlet newFileCell = navigator\n                .descendants(matching: .outlineRow)\n                .containing(.textField, identifier: \"ProjectNavigatorTableViewCell-FileName\")\n                .element(boundBy: 0)\n```\n\nShould be shortened to the following, which should be easier to read and intuit what the test is doing.\n\n```swift\nlet window = Query.getWindow(app)\nlet navigator = Query.Window.getProjectNavigator(window)\nlet newFileCell = Query.Navigator.getProjectNavigatorRow(fileTitle: \"FileName\", navigator)\n```\n\nThis isn't necessary for all tests, but useful for querying common regions like the project navigator, window, or \nutility area.\n"
  },
  {
    "path": "Configs/Alpha.xcconfig",
    "content": "//\n//  Alpha.xcconfig\n//  CodeEdit\n//\n//  Created by Lukas Pistrol on 14.01.23.\n//\n\n// Configuration settings file format documentation can be found at:\n// https://help.apple.com/xcode/#/dev745c5c974\n\nCE_APPICON_NAME = AppIconAlpha\nCE_VERSION_POSTFIX = -alpha\nCE_COPYRIGHT = Copyright © 2022-2025 CodeEdit\n"
  },
  {
    "path": "Configs/Beta.xcconfig",
    "content": "//\n//  Beta.xcconfig\n//  CodeEdit\n//\n//  Created by Lukas Pistrol on 14.01.23.\n//\n\n// Configuration settings file format documentation can be found at:\n// https://help.apple.com/xcode/#/dev745c5c974\n\nCE_APPICON_NAME = AppIconBeta\nCE_VERSION_POSTFIX = -beta\nCE_COPYRIGHT = Copyright © 2022-2025 CodeEdit\n"
  },
  {
    "path": "Configs/Debug.xcconfig",
    "content": "//\n//  Debug.xcconfig\n//  CodeEdit\n//\n//  Created by Lukas Pistrol on 14.01.23.\n//\n\n// Configuration settings file format documentation can be found at:\n// https://help.apple.com/xcode/#/dev745c5c974\n\nCE_APPICON_NAME = AppIconDev\nCE_VERSION_POSTFIX = -dev\nCE_COPYRIGHT = Copyright © 2022-2025 CodeEdit\n"
  },
  {
    "path": "Configs/Pre.xcconfig",
    "content": "//\n//  Pre.xcconfig\n//  CodeEdit\n//\n//  Created by Johnathan Baird on 3/11/24.\n//\n\n// Configuration settings file format documentation can be found at:\n// https://help.apple.com/xcode/#/dev745c5c974\n\nCE_APPICON_NAME = AppIconPre\nCE_VERSION_POSTFIX = -pre\nCE_COPYRIGHT = Copyright © 2022-2025 CodeEdit\n"
  },
  {
    "path": "Configs/Release.xcconfig",
    "content": "//\n//  Release.xcconfig\n//  CodeEdit\n//\n//  Created by Lukas Pistrol on 14.01.23.\n//\n\n// Configuration settings file format documentation can be found at:\n// https://help.apple.com/xcode/#/dev745c5c974\n\nCE_APPICON_NAME = AppIcon\n// CE_VERSION_POSTFIX = // this is a placeholder since we don't want a postfix in final release\nCE_COPYRIGHT = Copyright © 2022-2025 CodeEdit\n"
  },
  {
    "path": "DefaultThemes/Basic.cetheme",
    "content": "{\n  \"name\": \"basic\",\n  \"displayName\": \"Basic\",\n  \"description\": \"CodeEdit bundled theme.\",\n  \"author\": \"CodeEdit\",\n  \"version\": \"0.0.1\",\n  \"license\": \"MIT\",\n  \"distributionURL\": \"https://github.com/CodeEditApp/CodeEdit\",\n  \"type\": \"light\",\n  \"editor\": {\n    \"background\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"selection\": {\n      \"color\": \"#B2D7FF\"\n    },\n    \"insertionPoint\": {\n      \"color\": \"#000000\"\n    },\n    \"lineHighlight\": {\n      \"color\": \"#F5F5F6\"\n    },\n    \"invisibles\": {\n      \"color\": \"#D6D6D6\"\n    },\n    \"text\": {\n      \"color\": \"#000000\"\n    },\n    \"comments\": {\n      \"color\": \"#008F00\"\n    },\n    \"strings\": {\n      \"color\": \"#B4261A\"\n    },\n    \"characters\": {\n      \"color\": \"#000000\"\n    },\n    \"numbers\": {\n      \"color\": \"#000000\"\n    },\n    \"keywords\": {\n      \"color\": \"#0433FF\",\n      \"bold\": true\n    },\n    \"attributes\": {\n      \"color\": \"#000000\"\n    },\n    \"types\": {\n      \"color\": \"#02638C\"\n    },\n    \"variables\": {\n      \"color\": \"#057CB0\"\n    },\n    \"commands\": {\n      \"color\": \"#3495AF\"\n    },\n    \"values\": {\n      \"color\": \"#3495AF\"\n    }\n  },\n  \"terminal\": {\n    \"background\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"selection\": {\n      \"color\": \"#B2D7FF\"\n    },\n    \"cursor\": {\n      \"color\": \"#000000\"\n    },\n    \"text\": {\n      \"color\": \"#000000\"\n    },\n    \"boldText\": {\n      \"color\": \"#000000\"\n    },\n    \"black\": {\n      \"color\": \"#000000\"\n    },\n    \"red\": {\n      \"color\": \"#990000\"\n    },\n    \"green\": {\n      \"color\": \"#00A600\"\n    },\n    \"yellow\": {\n      \"color\": \"#999900\"\n    },\n    \"blue\": {\n      \"color\": \"#0000B2\"\n    },\n    \"magenta\": {\n      \"color\": \"#B200B2\"\n    },\n    \"cyan\": {\n      \"color\": \"#00A6B2\"\n    },\n    \"white\": {\n      \"color\": \"#BFBFBF\"\n    },\n    \"brightBlack\": {\n      \"color\": \"#666666\"\n    },\n    \"brightRed\": {\n      \"color\": \"#E50000\"\n    },\n    \"brightGreen\": {\n      \"color\": \"#00D900\"\n    },\n    \"brightYellow\": {\n      \"color\": \"#E5E500\"\n    },\n    \"brightBlue\": {\n      \"color\": \"#0000FF\"\n    },\n    \"brightMagenta\": {\n      \"color\": \"#E500E5\"\n    },\n    \"brightCyan\": {\n      \"color\": \"#00E5E5\"\n    },\n    \"brightWhite\": {\n      \"color\": \"#E5E5E5\"\n    }\n  }\n}\n"
  },
  {
    "path": "DefaultThemes/Civic.cetheme",
    "content": "{\n  \"name\": \"civic\",\n  \"displayName\": \"Civic\",\n  \"description\": \"CodeEdit bundled theme.\",\n  \"author\": \"CodeEdit\",\n  \"version\": \"0.0.1\",\n  \"license\": \"MIT\",\n  \"distributionURL\": \"https://github.com/CodeEditApp/CodeEdit\",\n  \"type\": \"dark\",\n  \"editor\": {\n    \"background\": {\n      \"color\": \"#292B36\"\n    },\n    \"selection\": {\n      \"color\": \"#445261\"\n    },\n    \"insertionPoint\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"lineHighlight\": {\n      \"color\": \"#353749\"\n    },\n    \"invisibles\": {\n      \"color\": \"#5F5F5F\"\n    },\n    \"text\": {\n      \"color\": \"#E7E8EB\"\n    },\n    \"comments\": {\n      \"color\": \"#51C34F\"\n    },\n    \"strings\": {\n      \"color\": \"#DE3A3C\"\n    },\n    \"characters\": {\n      \"color\": \"#8783BE\"\n    },\n    \"numbers\": {\n      \"color\": \"#00AAA3\"\n    },\n    \"keywords\": {\n      \"color\": \"#E12DA0\",\n      \"bold\": true\n    },\n    \"attributes\": {\n      \"color\": \"#68878F\"\n    },\n    \"types\": {\n      \"color\": \"#6BDFFF\"\n    },\n    \"variables\": {\n      \"color\": \"#4EB0CC\"\n    },\n    \"commands\": {\n      \"color\": \"#18B5B1\"\n    },\n    \"values\": {\n      \"color\": \"#29A09F\"\n    }\n  },\n  \"terminal\": {\n    \"background\": {\n      \"color\": \"#292B36\"\n    },\n    \"selection\": {\n      \"color\": \"#445261\"\n    },\n    \"cursor\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"text\": {\n      \"color\": \"#E7E8EB\"\n    },\n    \"boldText\": {\n      \"color\": \"#E7E8EB\"\n    },\n    \"black\": {\n      \"color\": \"#000000\"\n    },\n    \"red\": {\n      \"color\": \"#DE3A3C\"\n    },\n    \"green\": {\n      \"color\": \"#51C34F\"\n    },\n    \"yellow\": {\n      \"color\": \"#8783BE\"\n    },\n    \"blue\": {\n      \"color\": \"#4EB0CC\"\n    },\n    \"magenta\": {\n      \"color\": \"#E12DA0\"\n    },\n    \"cyan\": {\n      \"color\": \"#29A09F\"\n    },\n    \"white\": {\n      \"color\": \"#E7E8EB\"\n    },\n    \"brightBlack\": {\n      \"color\": \"#666666\"\n    },\n    \"brightRed\": {\n      \"color\": \"#DE3A3C\"\n    },\n    \"brightGreen\": {\n      \"color\": \"#51C34F\"\n    },\n    \"brightYellow\": {\n      \"color\": \"#8783BE\"\n    },\n    \"brightBlue\": {\n      \"color\": \"#4EB0CC\"\n    },\n    \"brightMagenta\": {\n      \"color\": \"#E500E5\"\n    },\n    \"brightCyan\": {\n      \"color\": \"#29A09F\"\n    },\n    \"brightWhite\": {\n      \"color\": \"#E7E8EB\"\n    }\n  }\n}\n"
  },
  {
    "path": "DefaultThemes/Classic (Dark).cetheme",
    "content": "{\n  \"name\": \"classic.dark\",\n  \"displayName\": \"Classic (Dark)\",\n  \"description\": \"CodeEdit bundled theme.\",\n  \"author\": \"CodeEdit\",\n  \"version\": \"0.0.1\",\n  \"license\": \"MIT\",\n  \"distributionURL\": \"https://github.com/CodeEditApp/CodeEdit\",\n  \"type\": \"dark\",\n  \"editor\": {\n    \"background\": {\n      \"color\": \"#292A30\"\n    },\n    \"selection\": {\n      \"color\": \"#646F83\"\n    },\n    \"insertionPoint\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"lineHighlight\": {\n      \"color\": \"#2F3239\"\n    },\n    \"invisibles\": {\n      \"color\": \"#53606E\"\n    },\n    \"text\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"comments\": {\n      \"color\": \"#84B360\"\n    },\n    \"strings\": {\n      \"color\": \"#FF8170\"\n    },\n    \"characters\": {\n      \"color\": \"#D9C97C\"\n    },\n    \"numbers\": {\n      \"color\": \"#D9C97C\"\n    },\n    \"keywords\": {\n      \"color\": \"#FF7AB2\",\n      \"bold\": true\n    },\n    \"attributes\": {\n      \"color\": \"#CC9768\"\n    },\n    \"types\": {\n      \"color\": \"#6BDFFF\"\n    },\n    \"variables\": {\n      \"color\": \"#4EB0CC\"\n    },\n    \"commands\": {\n      \"color\": \"#78C2B3\"\n    },\n    \"values\": {\n      \"color\": \"#B281EB\"\n    }\n  },\n  \"terminal\": {\n    \"background\": {\n      \"color\": \"#292A30\"\n    },\n    \"selection\": {\n      \"color\": \"#646F83\"\n    },\n    \"cursor\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"text\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"boldText\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"black\": {\n      \"color\": \"#1F1F24\"\n    },\n    \"red\": {\n      \"color\": \"#FF8170\"\n    },\n    \"green\": {\n      \"color\": \"#84B360\"\n    },\n    \"yellow\": {\n      \"color\": \"#D9C97C\"\n    },\n    \"blue\": {\n      \"color\": \"#4EB0CC\"\n    },\n    \"magenta\": {\n      \"color\": \"#B281EB\"\n    },\n    \"cyan\": {\n      \"color\": \"#78C2B3\"\n    },\n    \"white\": {\n      \"color\": \"#BFBFBF\"\n    },\n    \"brightBlack\": {\n      \"color\": \"#1F1F24\"\n    },\n    \"brightRed\": {\n      \"color\": \"#FF8170\"\n    },\n    \"brightGreen\": {\n      \"color\": \"#84B360\"\n    },\n    \"brightYellow\": {\n      \"color\": \"#D9C97C\"\n    },\n    \"brightBlue\": {\n      \"color\": \"#4EB0CC\"\n    },\n    \"brightMagenta\": {\n      \"color\": \"#B281EB\"\n    },\n    \"brightCyan\": {\n      \"color\": \"#78C2B3\"\n    },\n    \"brightWhite\": {\n      \"color\": \"#E5E5E5\"\n    }\n  }\n}\n"
  },
  {
    "path": "DefaultThemes/Classic (Light).cetheme",
    "content": "{\n  \"name\": \"classic.light\",\n  \"displayName\": \"Classic (Light)\",\n  \"description\": \"CodeEdit bundled theme.\",\n  \"author\": \"CodeEdit\",\n  \"version\": \"0.0.1\",\n  \"license\": \"MIT\",\n  \"distributionURL\": \"https://github.com/CodeEditApp/CodeEdit\",\n  \"type\": \"light\",\n  \"editor\": {\n    \"background\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"selection\": {\n      \"color\": \"#B2D7FF\"\n    },\n    \"insertionPoint\": {\n      \"color\": \"#000000\"\n    },\n    \"lineHighlight\": {\n      \"color\": \"#ECF5FF\"\n    },\n    \"invisibles\": {\n      \"color\": \"#D6D6D6\"\n    },\n    \"text\": {\n      \"color\": \"#000000\"\n    },\n    \"comments\": {\n      \"color\": \"#2D8504\"\n    },\n    \"strings\": {\n      \"color\": \"#D12F1B\"\n    },\n    \"characters\": {\n      \"color\": \"#272AD8\"\n    },\n    \"numbers\": {\n      \"color\": \"#272AD8\"\n    },\n    \"keywords\": {\n      \"color\": \"#AD3DA4\",\n      \"bold\": true\n    },\n    \"attributes\": {\n      \"color\": \"#947100\"\n    },\n    \"types\": {\n      \"color\": \"#03638C\"\n    },\n    \"variables\": {\n      \"color\": \"#057CB0\"\n    },\n    \"commands\": {\n      \"color\": \"#3F8087\"\n    },\n    \"values\": {\n      \"color\": \"#804FB8\"\n    }\n  },\n  \"terminal\": {\n    \"background\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"selection\": {\n      \"color\": \"#B2D7FF\"\n    },\n    \"cursor\": {\n      \"color\": \"#000000\"\n    },\n    \"text\": {\n      \"color\": \"#000000\"\n    },\n    \"boldText\": {\n      \"color\": \"#262626\"\n    },\n    \"black\": {\n      \"color\": \"#1F2024\"\n    },\n    \"red\": {\n      \"color\": \"#FF3B30\"\n    },\n    \"green\": {\n      \"color\": \"#28CD41\"\n    },\n    \"yellow\": {\n      \"color\": \"#FFCC00\"\n    },\n    \"blue\": {\n      \"color\": \"#007AFF\"\n    },\n    \"magenta\": {\n      \"color\": \"#AF52DE\"\n    },\n    \"cyan\": {\n      \"color\": \"#59ADC4\"\n    },\n    \"white\": {\n      \"color\": \"#D9D9D9\"\n    },\n    \"brightBlack\": {\n      \"color\": \"#8E8E93\"\n    },\n    \"brightRed\": {\n      \"color\": \"#FF3B30\"\n    },\n    \"brightGreen\": {\n      \"color\": \"#28CD41\"\n    },\n    \"brightYellow\": {\n      \"color\": \"#FFCC00\"\n    },\n    \"brightBlue\": {\n      \"color\": \"#007AFF\"\n    },\n    \"brightMagenta\": {\n      \"color\": \"#AF52DE\"\n    },\n    \"brightCyan\": {\n      \"color\": \"#55BEF0\"\n    },\n    \"brightWhite\": {\n      \"color\": \"#FFFFFF\"\n    }\n  }\n}\n"
  },
  {
    "path": "DefaultThemes/Default (Dark).cetheme",
    "content": "{\n  \"name\": \"default.dark\",\n  \"displayName\": \"Default (Dark)\",\n  \"description\": \"CodeEdit bundled theme.\",\n  \"author\": \"CodeEdit\",\n  \"version\": \"0.0.1\",\n  \"license\": \"MIT\",\n  \"distributionURL\": \"https://github.com/CodeEditApp/CodeEdit\",\n  \"type\": \"dark\",\n  \"editor\": {\n    \"background\": {\n      \"color\": \"#292A30\"\n    },\n    \"selection\": {\n      \"color\": \"#646F83\"\n    },\n    \"insertionPoint\": {\n      \"color\": \"#007AFF\"\n    },\n    \"lineHighlight\": {\n      \"color\": \"#2F3239\"\n    },\n    \"invisibles\": {\n      \"color\": \"#53606E\"\n    },\n    \"text\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"comments\": {\n      \"color\": \"#7F8C98\"\n    },\n    \"strings\": {\n      \"color\": \"#FF8170\"\n    },\n    \"characters\": {\n      \"color\": \"#D9C97C\"\n    },\n    \"numbers\": {\n      \"color\": \"#D9C97C\"\n    },\n    \"keywords\": {\n      \"color\": \"#FF7AB2\",\n      \"bold\": true\n    },\n    \"attributes\": {\n      \"color\": \"#CC9768\"\n    },\n    \"types\": {\n      \"color\": \"#6BDFFF\"\n    },\n    \"variables\": {\n      \"color\": \"#4EB0CC\"\n    },\n    \"commands\": {\n      \"color\": \"#78C2B3\"\n    },\n    \"values\": {\n      \"color\": \"#B281EB\"\n    }\n  },\n  \"terminal\": {\n    \"background\": {\n      \"color\": \"#292A30\"\n    },\n    \"selection\": {\n      \"color\": \"#646F83\"\n    },\n    \"cursor\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"text\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"boldText\": {\n      \"color\": \"#D9D9D9\"\n    },\n    \"black\": {\n      \"color\": \"#1F2024\"\n    },\n    \"red\": {\n      \"color\": \"#FF8170\"\n    },\n    \"green\": {\n      \"color\": \"#78C2B3\"\n    },\n    \"yellow\": {\n      \"color\": \"#D9C97C\"\n    },\n    \"blue\": {\n      \"color\": \"#B281EB\"\n    },\n    \"magenta\": {\n      \"color\": \"#FF7AB2\"\n    },\n    \"cyan\": {\n      \"color\": \"#4EB0CC\"\n    },\n    \"white\": {\n      \"color\": \"#D9D9D9\"\n    },\n    \"brightBlack\": {\n      \"color\": \"#8E8E93\"\n    },\n    \"brightRed\": {\n      \"color\": \"#FF8170\"\n    },\n    \"brightGreen\": {\n      \"color\": \"#78C2B3\"\n    },\n    \"brightYellow\": {\n      \"color\": \"#D9C97C\"\n    },\n    \"brightBlue\": {\n      \"color\": \"#B281EB\"\n    },\n    \"brightMagenta\": {\n      \"color\": \"#FF7AB2\"\n    },\n    \"brightCyan\": {\n      \"color\": \"#4EB0CC\"\n    },\n    \"brightWhite\": {\n      \"color\": \"#FFFFFF\"\n    }\n  }\n}\n"
  },
  {
    "path": "DefaultThemes/Default (Light).cetheme",
    "content": "{\n  \"name\": \"default.light\",\n  \"displayName\": \"Default (Light)\",\n  \"description\": \"CodeEdit bundled theme.\",\n  \"author\": \"CodeEdit\",\n  \"version\": \"0.0.1\",\n  \"license\": \"MIT\",\n  \"distributionURL\": \"https://github.com/CodeEditApp/CodeEdit\",\n  \"type\": \"light\",\n  \"editor\": {\n    \"background\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"selection\": {\n      \"color\": \"#B2D7FF\"\n    },\n    \"insertionPoint\": {\n      \"color\": \"#007AFF\"\n    },\n    \"lineHighlight\": {\n      \"color\": \"#ECF5FF\"\n    },\n    \"invisibles\": {\n      \"color\": \"#D6D6D6\"\n    },\n    \"text\": {\n      \"color\": \"#000000\"\n    },\n    \"comments\": {\n      \"color\": \"#707F8C\"\n    },\n    \"strings\": {\n      \"color\": \"#D12F1B\"\n    },\n    \"characters\": {\n      \"color\": \"#272AD8\"\n    },\n    \"numbers\": {\n      \"color\": \"#272AD8\"\n    },\n    \"keywords\": {\n      \"color\": \"#AD3DA4\",\n      \"bold\": true\n    },\n    \"attributes\": {\n      \"color\": \"#947100\"\n    },\n    \"types\": {\n      \"color\": \"#02638C\"\n    },\n    \"variables\": {\n      \"color\": \"#057CB0\"\n    },\n    \"commands\": {\n      \"color\": \"#3F8087\"\n    },\n    \"values\": {\n      \"color\": \"#804FB8\"\n    }\n  },\n  \"terminal\": {\n    \"background\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"selection\": {\n      \"color\": \"#B2D7FF\"\n    },\n    \"cursor\": {\n      \"color\": \"#000000\"\n    },\n    \"text\": {\n      \"color\": \"#000000\"\n    },\n    \"boldText\": {\n      \"color\": \"#262626\"\n    },\n    \"black\": {\n      \"color\": \"#1F2024\"\n    },\n    \"red\": {\n      \"color\": \"#D12F1B\"\n    },\n    \"green\": {\n      \"color\": \"#3F8087\"\n    },\n    \"yellow\": {\n      \"color\": \"#947100\"\n    },\n    \"blue\": {\n      \"color\": \"#272AD8\"\n    },\n    \"magenta\": {\n      \"color\": \"#804FB8\"\n    },\n    \"cyan\": {\n      \"color\": \"#02638C\"\n    },\n    \"white\": {\n      \"color\": \"#D9D9D9\"\n    },\n    \"brightBlack\": {\n      \"color\": \"#8E8E93\"\n    },\n    \"brightRed\": {\n      \"color\": \"#D12F1B\"\n    },\n    \"brightGreen\": {\n      \"color\": \"#3F8087\"\n    },\n    \"brightYellow\": {\n      \"color\": \"#947100\"\n    },\n    \"brightBlue\": {\n      \"color\": \"#272AD8\"\n    },\n    \"brightMagenta\": {\n      \"color\": \"#804FB8\"\n    },\n    \"brightCyan\": {\n      \"color\": \"#02638C\"\n    },\n    \"brightWhite\": {\n      \"color\": \"#FFFFFF\"\n    }\n  }\n}\n"
  },
  {
    "path": "DefaultThemes/Dusk.cetheme",
    "content": "{\n  \"name\": \"dusk\",\n  \"displayName\": \"Dusk\",\n  \"description\": \"CodeEdit bundled theme.\",\n  \"author\": \"CodeEdit\",\n  \"version\": \"0.0.1\",\n  \"license\": \"MIT\",\n  \"distributionURL\": \"https://github.com/CodeEditApp/CodeEdit\",\n  \"type\": \"dark\",\n  \"editor\": {\n    \"background\": {\n      \"color\": \"#282B35\"\n    },\n    \"selection\": {\n      \"color\": \"#67675C\"\n    },\n    \"insertionPoint\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"lineHighlight\": {\n      \"color\": \"#3B3B3C\"\n    },\n    \"invisibles\": {\n      \"color\": \"#5F5F5F\"\n    },\n    \"text\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"comments\": {\n      \"color\": \"#4DBF56\"\n    },\n    \"strings\": {\n      \"color\": \"#E44448\"\n    },\n    \"characters\": {\n      \"color\": \"#8B84CF\"\n    },\n    \"numbers\": {\n      \"color\": \"#8B84CF\"\n    },\n    \"keywords\": {\n      \"color\": \"#C2349B\",\n      \"bold\": true\n    },\n    \"attributes\": {\n      \"color\": \"#67878F\"\n    },\n    \"types\": {\n      \"color\": \"#6BDFFF\"\n    },\n    \"variables\": {\n      \"color\": \"#4EB0CC\"\n    },\n    \"commands\": {\n      \"color\": \"#93C86A\"\n    },\n    \"values\": {\n      \"color\": \"#00AFCA\"\n    }\n  },\n  \"terminal\": {\n    \"background\": {\n      \"color\": \"#282B35\"\n    },\n    \"selection\": {\n      \"color\": \"#67675C\"\n    },\n    \"cursor\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"text\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"boldText\": {\n      \"color\": \"#D9D9D9\"\n    },\n    \"black\": {\n      \"color\": \"#1F2024\"\n    },\n    \"red\": {\n      \"color\": \"#E44448\"\n    },\n    \"green\": {\n      \"color\": \"#93C86A\"\n    },\n    \"yellow\": {\n      \"color\": \"#FFCC00\"\n    },\n    \"blue\": {\n      \"color\": \"#8B84CF\"\n    },\n    \"magenta\": {\n      \"color\": \"#C2349B\"\n    },\n    \"cyan\": {\n      \"color\": \"#4EB0CC\"\n    },\n    \"white\": {\n      \"color\": \"#D9D9D9\"\n    },\n    \"brightBlack\": {\n      \"color\": \"#8E8E93\"\n    },\n    \"brightRed\": {\n      \"color\": \"#E44448\"\n    },\n    \"brightGreen\": {\n      \"color\": \"#93C86A\"\n    },\n    \"brightYellow\": {\n      \"color\": \"#FFCC00\"\n    },\n    \"brightBlue\": {\n      \"color\": \"#8B84CF\"\n    },\n    \"brightMagenta\": {\n      \"color\": \"#C2349B\"\n    },\n    \"brightCyan\": {\n      \"color\": \"#4EB0CC\"\n    },\n    \"brightWhite\": {\n      \"color\": \"#FFFFFF\"\n    }\n  }\n}\n"
  },
  {
    "path": "DefaultThemes/GitHub (Dark).cetheme",
    "content": "{\n  \"name\": \"github.dark\",\n  \"displayName\": \"GitHub (Dark)\",\n  \"description\": \"CodeEdit bundled theme.\",\n  \"author\": \"CodeEdit\",\n  \"version\": \"0.0.1\",\n  \"license\": \"MIT\",\n  \"distributionURL\": \"https://github.com/CodeEditApp/CodeEdit\",\n  \"type\": \"dark\",\n  \"editor\": {\n    \"background\": {\n      \"color\": \"#0D1117\"\n    },\n    \"selection\": {\n      \"color\": \"#1E4273\"\n    },\n    \"insertionPoint\": {\n      \"color\": \"#C9D1D9\"\n    },\n    \"lineHighlight\": {\n      \"color\": \"#23252B\"\n    },\n    \"invisibles\": {\n      \"color\": \"#424D5B\"\n    },\n    \"text\": {\n      \"color\": \"#C9D1D9\"\n    },\n    \"comments\": {\n      \"color\": \"#8B949E\"\n    },\n    \"strings\": {\n      \"color\": \"#A5D6FF\"\n    },\n    \"characters\": {\n      \"color\": \"#79C0FF\"\n    },\n    \"numbers\": {\n      \"color\": \"#79C0FF\"\n    },\n    \"keywords\": {\n      \"color\": \"#FF7B72\",\n      \"bold\": true\n    },\n    \"attributes\": {\n      \"color\": \"#79C0FF\"\n    },\n    \"types\": {\n      \"color\": \"#79C0FF\"\n    },\n    \"variables\": {\n      \"color\": \"#D2A8FF\"\n    },\n    \"commands\": {\n      \"color\": \"#D2A8FF\"\n    },\n    \"values\": {\n      \"color\": \"#79C0FF\"\n    }\n  },\n  \"terminal\": {\n    \"background\": {\n      \"color\": \"#0D1117\"\n    },\n    \"selection\": {\n      \"color\": \"#1E4273\"\n    },\n    \"cursor\": {\n      \"color\": \"#C9D1D9\"\n    },\n    \"text\": {\n      \"color\": \"#C9D1D9\"\n    },\n    \"boldText\": {\n      \"color\": \"#C9D1D9\"\n    },\n    \"black\": {\n      \"color\": \"#1F2024\"\n    },\n    \"red\": {\n      \"color\": \"#FF7B72\"\n    },\n    \"green\": {\n      \"color\": \"#7EE787\"\n    },\n    \"yellow\": {\n      \"color\": \"#F2CC60\"\n    },\n    \"blue\": {\n      \"color\": \"#79C0FF\"\n    },\n    \"magenta\": {\n      \"color\": \"#D2A8FF\"\n    },\n    \"cyan\": {\n      \"color\": \"#79C0FF\"\n    },\n    \"white\": {\n      \"color\": \"#D9D9D9\"\n    },\n    \"brightBlack\": {\n      \"color\": \"#8E8E93\"\n    },\n    \"brightRed\": {\n      \"color\": \"#FF7B72\"\n    },\n    \"brightGreen\": {\n      \"color\": \"#7EE787\"\n    },\n    \"brightYellow\": {\n      \"color\": \"#F2CC60\"\n    },\n    \"brightBlue\": {\n      \"color\": \"#79C0FF\"\n    },\n    \"brightMagenta\": {\n      \"color\": \"#D2A8FF\"\n    },\n    \"brightCyan\": {\n      \"color\": \"#79C0FF\"\n    },\n    \"brightWhite\": {\n      \"color\": \"#FFFFFF\"\n    }\n  }\n}\n"
  },
  {
    "path": "DefaultThemes/GitHub (Light).cetheme",
    "content": "{\n  \"name\": \"github.light\",\n  \"displayName\": \"GitHub (Light)\",\n  \"description\": \"CodeEdit bundled theme.\",\n  \"author\": \"CodeEdit\",\n  \"version\": \"0.0.1\",\n  \"license\": \"MIT\",\n  \"distributionURL\": \"https://github.com/CodeEditApp/CodeEdit\",\n  \"type\": \"light\",\n  \"editor\": {\n    \"background\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"selection\": {\n      \"color\": \"#B4D6F5\"\n    },\n    \"insertionPoint\": {\n      \"color\": \"#24292F\"\n    },\n    \"lineHighlight\": {\n      \"color\": \"#E8F2FF\"\n    },\n    \"invisibles\": {\n      \"color\": \"#CCCCCC\"\n    },\n    \"text\": {\n      \"color\": \"#24292F\"\n    },\n    \"comments\": {\n      \"color\": \"#6E7781\"\n    },\n    \"strings\": {\n      \"color\": \"#0A3069\"\n    },\n    \"characters\": {\n      \"color\": \"#0550AE\"\n    },\n    \"numbers\": {\n      \"color\": \"#0550AE\"\n    },\n    \"keywords\": {\n      \"color\": \"#CF222E\",\n      \"bold\": true\n    },\n    \"attributes\": {\n      \"color\": \"#0550AE\"\n    },\n    \"types\": {\n      \"color\": \"#0550AE\"\n    },\n    \"variables\": {\n      \"color\": \"#8250DF\"\n    },\n    \"commands\": {\n      \"color\": \"#8250df\"\n    },\n    \"values\": {\n      \"color\": \"#0550AE\"\n    }\n  },\n  \"terminal\": {\n    \"background\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"selection\": {\n      \"color\": \"#B4D6F5\"\n    },\n    \"cursor\": {\n      \"color\": \"#24292F\"\n    },\n    \"text\": {\n      \"color\": \"#24292f\"\n    },\n    \"boldText\": {\n      \"color\": \"#24292F\"\n    },\n    \"black\": {\n      \"color\": \"#1F2024\"\n    },\n    \"red\": {\n      \"color\": \"#CF222E\"\n    },\n    \"green\": {\n      \"color\": \"#7EE787\"\n    },\n    \"yellow\": {\n      \"color\": \"#F2CC60\"\n    },\n    \"blue\": {\n      \"color\": \"#79C0FF\"\n    },\n    \"magenta\": {\n      \"color\": \"#8250DF\"\n    },\n    \"cyan\": {\n      \"color\": \"#0550AE\"\n    },\n    \"white\": {\n      \"color\": \"#D9D9D9\"\n    },\n    \"brightBlack\": {\n      \"color\": \"#8E8E93\"\n    },\n    \"brightRed\": {\n      \"color\": \"#CF222E\"\n    },\n    \"brightGreen\": {\n      \"color\": \"#116329\"\n    },\n    \"brightYellow\": {\n      \"color\": \"#B38300\"\n    },\n    \"brightBlue\": {\n      \"color\": \"#79C0FF\"\n    },\n    \"brightMagenta\": {\n      \"color\": \"#8250DF\"\n    },\n    \"brightCyan\": {\n      \"color\": \"#0550AE\"\n    },\n    \"brightWhite\": {\n      \"color\": \"#FFFFFF\"\n    }\n  }\n}\n"
  },
  {
    "path": "DefaultThemes/High Contrast (Dark).cetheme",
    "content": "{\n  \"name\": \"high.contrast.dark\",\n  \"displayName\": \"High Contrast (Dark)\",\n  \"description\": \"CodeEdit bundled theme.\",\n  \"author\": \"CodeEdit\",\n  \"version\": \"0.0.1\",\n  \"license\": \"MIT\",\n  \"distributionURL\": \"https://github.com/CodeEditApp/CodeEdit\",\n  \"type\": \"dark\",\n  \"editor\": {\n    \"background\": {\n      \"color\": \"#1F1F24\"\n    },\n    \"selection\": {\n      \"color\": \"#434D5D\"\n    },\n    \"insertionPoint\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"lineHighlight\": {\n      \"color\": \"#2F3139\"\n    },\n    \"invisibles\": {\n      \"color\": \"#53606E\"\n    },\n    \"text\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"comments\": {\n      \"color\": \"#8DBF67\"\n    },\n    \"strings\": {\n      \"color\": \"#FF8A7A\"\n    },\n    \"characters\": {\n      \"color\": \"#D9C668\"\n    },\n    \"numbers\": {\n      \"color\": \"#D9C668\"\n    },\n    \"keywords\": {\n      \"color\": \"#FF85B8\",\n      \"bold\": true\n    },\n    \"attributes\": {\n      \"color\": \"#E8B68B\"\n    },\n    \"types\": {\n      \"color\": \"#6BDFFF\"\n    },\n    \"variables\": {\n      \"color\": \"#4EC4E6\"\n    },\n    \"commands\": {\n      \"color\": \"#83C9BC\"\n    },\n    \"values\": {\n      \"color\": \"#CDA1FF\"\n    }\n  },\n  \"terminal\": {\n    \"background\": {\n      \"color\": \"#1F1F24\"\n    },\n    \"selection\": {\n      \"color\": \"#646F83\"\n    },\n    \"cursor\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"text\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"boldText\": {\n      \"color\": \"#D9D9D9\"\n    },\n    \"black\": {\n      \"color\": \"#1F2024\"\n    },\n    \"red\": {\n      \"color\": \"#FF8A7A\"\n    },\n    \"green\": {\n      \"color\": \"#8DBF67\"\n    },\n    \"yellow\": {\n      \"color\": \"#FFCC00\"\n    },\n    \"blue\": {\n      \"color\": \"#4EC4E6\"\n    },\n    \"magenta\": {\n      \"color\": \"#FF85B8\"\n    },\n    \"cyan\": {\n      \"color\": \"#6BDFFF\"\n    },\n    \"white\": {\n      \"color\": \"#D9D9D9\"\n    },\n    \"brightBlack\": {\n      \"color\": \"#8E8E93\"\n    },\n    \"brightRed\": {\n      \"color\": \"#FF8A7A\"\n    },\n    \"brightGreen\": {\n      \"color\": \"#8DBF67\"\n    },\n    \"brightYellow\": {\n      \"color\": \"#FFCC00\"\n    },\n    \"brightBlue\": {\n      \"color\": \"#CDA1FF\"\n    },\n    \"brightMagenta\": {\n      \"color\": \"#FF85B8\"\n    },\n    \"brightCyan\": {\n      \"color\": \"#4EC4E6\"\n    },\n    \"brightWhite\": {\n      \"color\": \"#FFFFFF\"\n    }\n  }\n}\n"
  },
  {
    "path": "DefaultThemes/High Contrast (Light).cetheme",
    "content": "{\n  \"name\": \"high.contrast.light\",\n  \"displayName\": \"High Contrast (Light)\",\n  \"description\": \"CodeEdit bundled theme.\",\n  \"author\": \"CodeEdit\",\n  \"version\": \"0.0.1\",\n  \"license\": \"MIT\",\n  \"distributionURL\": \"https://github.com/CodeEditApp/CodeEdit\",\n  \"type\": \"light\",\n  \"editor\": {\n    \"background\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"selection\": {\n      \"color\": \"#B2D7FF\"\n    },\n    \"insertionPoint\": {\n      \"color\": \"#000000\"\n    },\n    \"lineHighlight\": {\n      \"color\": \"#ECF5FF\"\n    },\n    \"invisibles\": {\n      \"color\": \"#D6D6D6\"\n    },\n    \"text\": {\n      \"color\": \"#000000\"\n    },\n    \"comments\": {\n      \"color\": \"#1F6300\"\n    },\n    \"strings\": {\n      \"color\": \"#AD1805\"\n    },\n    \"characters\": {\n      \"color\": \"#272AD8\"\n    },\n    \"numbers\": {\n      \"color\": \"#272AD8\"\n    },\n    \"keywords\": {\n      \"color\": \"#9C2191\",\n      \"bold\": true\n    },\n    \"attributes\": {\n      \"color\": \"#6E5400\"\n    },\n    \"types\": {\n      \"color\": \"#003F73\"\n    },\n    \"variables\": {\n      \"color\": \"#0058A1\"\n    },\n    \"commands\": {\n      \"color\": \"#294B4E\"\n    },\n    \"values\": {\n      \"color\": \"#330090\"\n    }\n  },\n  \"terminal\": {\n    \"background\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"selection\": {\n      \"color\": \"#B2D7FF\"\n    },\n    \"cursor\": {\n      \"color\": \"#000000\"\n    },\n    \"text\": {\n      \"color\": \"#000000\"\n    },\n    \"boldText\": {\n      \"color\": \"#262626\"\n    },\n    \"black\": {\n      \"color\": \"#1F2024\"\n    },\n    \"red\": {\n      \"color\": \"#AD1805\"\n    },\n    \"green\": {\n      \"color\": \"#1F6300\"\n    },\n    \"yellow\": {\n      \"color\": \"#6E5400\"\n    },\n    \"blue\": {\n      \"color\": \"#272AD8\"\n    },\n    \"magenta\": {\n      \"color\": \"#9C2191\"\n    },\n    \"cyan\": {\n      \"color\": \"#0058A1\"\n    },\n    \"white\": {\n      \"color\": \"#D9D9D9\"\n    },\n    \"brightBlack\": {\n      \"color\": \"#8E8E93\"\n    },\n    \"brightRed\": {\n      \"color\": \"#AD1805\"\n    },\n    \"brightGreen\": {\n      \"color\": \"#1F6300\"\n    },\n    \"brightYellow\": {\n      \"color\": \"#6E5400\"\n    },\n    \"brightBlue\": {\n      \"color\": \"#272AD8\"\n    },\n    \"brightMagenta\": {\n      \"color\": \"#9C2191\"\n    },\n    \"brightCyan\": {\n      \"color\": \"#0058A1\"\n    },\n    \"brightWhite\": {\n      \"color\": \"#FFFFFF\"\n    }\n  }\n}\n"
  },
  {
    "path": "DefaultThemes/Low Key.cetheme",
    "content": "{\n  \"name\": \"low.key\",\n  \"displayName\": \"Low Key\",\n  \"description\": \"CodeEdit bundled theme.\",\n  \"author\": \"CodeEdit\",\n  \"version\": \"0.0.1\",\n  \"license\": \"MIT\",\n  \"distributionURL\": \"https://github.com/CodeEditApp/CodeEdit\",\n  \"type\": \"light\",\n  \"editor\": {\n    \"background\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"selection\": {\n      \"color\": \"#D8DDDA\"\n    },\n    \"insertionPoint\": {\n      \"color\": \"#000000\"\n    },\n    \"lineHighlight\": {\n      \"color\": \"#F5F7F6\"\n    },\n    \"invisibles\": {\n      \"color\": \"#C0C0C0\"\n    },\n    \"text\": {\n      \"color\": \"#000000\"\n    },\n    \"comments\": {\n      \"color\": \"#546348\"\n    },\n    \"strings\": {\n      \"color\": \"#843E64\"\n    },\n    \"characters\": {\n      \"color\": \"#323E7D\"\n    },\n    \"numbers\": {\n      \"color\": \"#323E7D\"\n    },\n    \"keywords\": {\n      \"color\": \"#323E7D\",\n      \"bold\": true\n    },\n    \"attributes\": {\n      \"color\": \"#255E22\"\n    },\n    \"types\": {\n      \"color\": \"#02638C\"\n    },\n    \"variables\": {\n      \"color\": \"#0F68A0\"\n    },\n    \"commands\": {\n      \"color\": \"#587EA8\"\n    },\n    \"values\": {\n      \"color\": \"#587EA8\"\n    }\n  },\n  \"terminal\": {\n    \"background\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"selection\": {\n      \"color\": \"#D8DDDA\"\n    },\n    \"cursor\": {\n      \"color\": \"#000000\"\n    },\n    \"text\": {\n      \"color\": \"#000000\"\n    },\n    \"boldText\": {\n      \"color\": \"#000000\"\n    },\n    \"black\": {\n      \"color\": \"#1F2024\"\n    },\n    \"red\": {\n      \"color\": \"#843E64\"\n    },\n    \"green\": {\n      \"color\": \"#255E22\"\n    },\n    \"yellow\": {\n      \"color\": \"#546348\"\n    },\n    \"blue\": {\n      \"color\": \"#587EA8\"\n    },\n    \"magenta\": {\n      \"color\": \"#323E7D\"\n    },\n    \"cyan\": {\n      \"color\": \"#02638C\"\n    },\n    \"white\": {\n      \"color\": \"#D9D9D9\"\n    },\n    \"brightBlack\": {\n      \"color\": \"#8E8E93\"\n    },\n    \"brightRed\": {\n      \"color\": \"#843E64\"\n    },\n    \"brightGreen\": {\n      \"color\": \"#255E22\"\n    },\n    \"brightYellow\": {\n      \"color\": \"#546348\"\n    },\n    \"brightBlue\": {\n      \"color\": \"#587EA8\"\n    },\n    \"brightMagenta\": {\n      \"color\": \"#323E7D\"\n    },\n    \"brightCyan\": {\n      \"color\": \"#02638C\"\n    },\n    \"brightWhite\": {\n      \"color\": \"#FFFFFF\"\n    }\n  }\n}\n"
  },
  {
    "path": "DefaultThemes/Midnight.cetheme",
    "content": "{\n  \"name\": \"midnight\",\n  \"displayName\": \"Midnight\",\n  \"description\": \"CodeEdit bundled theme.\",\n  \"author\": \"CodeEdit\",\n  \"version\": \"0.0.1\",\n  \"license\": \"MIT\",\n  \"distributionURL\": \"https://github.com/CodeEditApp/CodeEdit\",\n  \"type\": \"dark\",\n  \"editor\": {\n    \"background\": {\n      \"color\": \"#000000\"\n    },\n    \"selection\": {\n      \"color\": \"#5D5952\"\n    },\n    \"insertionPoint\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"lineHighlight\": {\n      \"color\": \"#232121\"\n    },\n    \"invisibles\": {\n      \"color\": \"#424242\"\n    },\n    \"text\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"comments\": {\n      \"color\": \"#4BD157\"\n    },\n    \"strings\": {\n      \"color\": \"#FF4647\"\n    },\n    \"characters\": {\n      \"color\": \"#8B87FF\"\n    },\n    \"numbers\": {\n      \"color\": \"#8B87FF\"\n    },\n    \"keywords\": {\n      \"color\": \"#DE38A6\",\n      \"bold\": true\n    },\n    \"attributes\": {\n      \"color\": \"#3B5AAB\"\n    },\n    \"types\": {\n      \"color\": \"#6BDFFF\"\n    },\n    \"variables\": {\n      \"color\": \"#4EB0CC\"\n    },\n    \"commands\": {\n      \"color\": \"#09FA95\"\n    },\n    \"values\": {\n      \"color\": \"#00B1FF\"\n    }\n  },\n  \"terminal\": {\n    \"background\": {\n      \"color\": \"#000000\"\n    },\n    \"selection\": {\n      \"color\": \"#5D5952\"\n    },\n    \"cursor\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"text\": {\n      \"color\": \"#D9D9D9\"\n    },\n    \"boldText\": {\n      \"color\": \"#D9D9D9\"\n    },\n    \"black\": {\n      \"color\": \"#1F2024\"\n    },\n    \"red\": {\n      \"color\": \"#FF4647\"\n    },\n    \"green\": {\n      \"color\": \"#4BD157\"\n    },\n    \"yellow\": {\n      \"color\": \"#EB905A\"\n    },\n    \"blue\": {\n      \"color\": \"#00B1FF\"\n    },\n    \"magenta\": {\n      \"color\": \"#DE38A6\"\n    },\n    \"cyan\": {\n      \"color\": \"#6BDFFF\"\n    },\n    \"white\": {\n      \"color\": \"#D9D9D9\"\n    },\n    \"brightBlack\": {\n      \"color\": \"#8E8E93\"\n    },\n    \"brightRed\": {\n      \"color\": \"#FF4647\"\n    },\n    \"brightGreen\": {\n      \"color\": \"#4BD157\"\n    },\n    \"brightYellow\": {\n      \"color\": \"#EB905A\"\n    },\n    \"brightBlue\": {\n      \"color\": \"#00B1FF\"\n    },\n    \"brightMagenta\": {\n      \"color\": \"#DE38A6\"\n    },\n    \"brightCyan\": {\n      \"color\": \"#6BDFFF\"\n    },\n    \"brightWhite\": {\n      \"color\": \"#FFFFFF\"\n    }\n  }\n}\n"
  },
  {
    "path": "DefaultThemes/Presentation (Dark).cetheme",
    "content": "{\n  \"name\": \"presentation.dark\",\n  \"displayName\": \"Presentation (Dark)\",\n  \"description\": \"CodeEdit bundled theme.\",\n  \"author\": \"CodeEdit\",\n  \"version\": \"0.0.1\",\n  \"license\": \"MIT\",\n  \"distributionURL\": \"https://github.com/CodeEditApp/CodeEdit\",\n  \"type\": \"dark\",\n  \"editor\": {\n    \"background\": {\n      \"color\": \"#202025\"\n    },\n    \"selection\": {\n      \"color\": \"#646F83\"\n    },\n    \"insertionPoint\": {\n      \"color\": \"#007AFF\"\n    },\n    \"lineHighlight\": {\n      \"color\": \"#444551\"\n    },\n    \"invisibles\": {\n      \"color\": \"#5F5F5F\"\n    },\n    \"text\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"comments\": {\n      \"color\": \"#7F8C99\"\n    },\n    \"strings\": {\n      \"color\": \"#FF5F63\"\n    },\n    \"characters\": {\n      \"color\": \"#FFEA80\"\n    },\n    \"numbers\": {\n      \"color\": \"#FFEA80\"\n    },\n    \"keywords\": {\n      \"color\": \"#F7439D\",\n      \"bold\": true\n    },\n    \"attributes\": {\n      \"color\": \"#E7AD78\"\n    },\n    \"types\": {\n      \"color\": \"#75E1FF\"\n    },\n    \"variables\": {\n      \"color\": \"#3EBDE0\"\n    },\n    \"commands\": {\n      \"color\": \"#64D7C0\"\n    },\n    \"values\": {\n      \"color\": \"#BB81FF\"\n    }\n  },\n  \"terminal\": {\n    \"background\": {\n      \"color\": \"#202025\"\n    },\n    \"selection\": {\n      \"color\": \"#646F83\"\n    },\n    \"cursor\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"text\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"boldText\": {\n      \"color\": \"#D9D9D9\"\n    },\n    \"black\": {\n      \"color\": \"#1F2024\"\n    },\n    \"red\": {\n      \"color\": \"#FF5F63\"\n    },\n    \"green\": {\n      \"color\": \"#64D7C0\"\n    },\n    \"yellow\": {\n      \"color\": \"#FFEA80\"\n    },\n    \"blue\": {\n      \"color\": \"#3EBDE0\"\n    },\n    \"magenta\": {\n      \"color\": \"#F7439D\"\n    },\n    \"cyan\": {\n      \"color\": \"#75E1FF\"\n    },\n    \"white\": {\n      \"color\": \"#D9D9D9\"\n    },\n    \"brightBlack\": {\n      \"color\": \"#8E8E93\"\n    },\n    \"brightRed\": {\n      \"color\": \"#FF5F63\"\n    },\n    \"brightGreen\": {\n      \"color\": \"#64D7C0\"\n    },\n    \"brightYellow\": {\n      \"color\": \"#FFEA80\"\n    },\n    \"brightBlue\": {\n      \"color\": \"#3EBDE0\"\n    },\n    \"brightMagenta\": {\n      \"color\": \"#F7439D\"\n    },\n    \"brightCyan\": {\n      \"color\": \"#75E1FF\"\n    },\n    \"brightWhite\": {\n      \"color\": \"#FFFFFF\"\n    }\n  }\n}\n"
  },
  {
    "path": "DefaultThemes/Presentation (Light).cetheme",
    "content": "{\n  \"name\": \"presentation.light\",\n  \"displayName\": \"Presentation (Light)\",\n  \"description\": \"CodeEdit bundled theme.\",\n  \"author\": \"CodeEdit\",\n  \"version\": \"0.0.1\",\n  \"license\": \"MIT\",\n  \"distributionURL\": \"https://github.com/CodeEditApp/CodeEdit\",\n  \"type\": \"light\",\n  \"editor\": {\n    \"background\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"selection\": {\n      \"color\": \"#D1E3FF\"\n    },\n    \"insertionPoint\": {\n      \"color\": \"#007AFF\"\n    },\n    \"lineHighlight\": {\n      \"color\": \"#ECF5FF\"\n    },\n    \"invisibles\": {\n      \"color\": \"#D6D6D6\"\n    },\n    \"text\": {\n      \"color\": \"#000000\"\n    },\n    \"comments\": {\n      \"color\": \"#69737E\"\n    },\n    \"strings\": {\n      \"color\": \"#C91B13\"\n    },\n    \"characters\": {\n      \"color\": \"#0435FF\"\n    },\n    \"numbers\": {\n      \"color\": \"#0435FF\"\n    },\n    \"keywords\": {\n      \"color\": \"#C32275\",\n      \"bold\": true\n    },\n    \"attributes\": {\n      \"color\": \"#967E34\"\n    },\n    \"types\": {\n      \"color\": \"#005C88\"\n    },\n    \"variables\": {\n      \"color\": \"#057CB0\"\n    },\n    \"commands\": {\n      \"color\": \"#49919B\"\n    },\n    \"values\": {\n      \"color\": \"#703DAA\"\n    }\n  },\n  \"terminal\": {\n    \"background\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"selection\": {\n      \"color\": \"#D1E3FF\"\n    },\n    \"cursor\": {\n      \"color\": \"#000000\"\n    },\n    \"text\": {\n      \"color\": \"#000000\"\n    },\n    \"boldText\": {\n      \"color\": \"#262626\"\n    },\n    \"black\": {\n      \"color\": \"#1F2024\"\n    },\n    \"red\": {\n      \"color\": \"#C91B13\"\n    },\n    \"green\": {\n      \"color\": \"#005C88\"\n    },\n    \"yellow\": {\n      \"color\": \"#967E34\"\n    },\n    \"blue\": {\n      \"color\": \"#0435FF\"\n    },\n    \"magenta\": {\n      \"color\": \"#C32275\"\n    },\n    \"cyan\": {\n      \"color\": \"#49919B\"\n    },\n    \"white\": {\n      \"color\": \"#D9D9D9\"\n    },\n    \"brightBlack\": {\n      \"color\": \"#8E8E93\"\n    },\n    \"brightRed\": {\n      \"color\": \"#C91B13\"\n    },\n    \"brightGreen\": {\n      \"color\": \"#005C88\"\n    },\n    \"brightYellow\": {\n      \"color\": \"#967E34\"\n    },\n    \"brightBlue\": {\n      \"color\": \"#0435FF\"\n    },\n    \"brightMagenta\": {\n      \"color\": \"#C32275\"\n    },\n    \"brightCyan\": {\n      \"color\": \"#49919B\"\n    },\n    \"brightWhite\": {\n      \"color\": \"#FFFFFF\"\n    }\n  }\n}\n"
  },
  {
    "path": "DefaultThemes/Solarized (Dark).cetheme",
    "content": "{\n  \"name\": \"solarized.dark\",\n  \"displayName\": \"Solarized (Dark)\",\n  \"description\": \"CodeEdit bundled theme.\",\n  \"author\": \"CodeEdit\",\n  \"version\": \"0.0.1\",\n  \"license\": \"MIT\",\n  \"distributionURL\": \"https://github.com/CodeEditApp/CodeEdit\",\n  \"type\": \"dark\",\n  \"editor\": {\n    \"background\": {\n      \"color\": \"#002B36\"\n    },\n    \"selection\": {\n      \"color\": \"#586E75\"\n    },\n    \"insertionPoint\": {\n      \"color\": \"#839496\"\n    },\n    \"lineHighlight\": {\n      \"color\": \"#073642\"\n    },\n    \"invisibles\": {\n      \"color\": \"#073642\"\n    },\n    \"text\": {\n      \"color\": \"#839496\"\n    },\n    \"comments\": {\n      \"color\": \"#586E75\"\n    },\n    \"strings\": {\n      \"color\": \"#2AA198\"\n    },\n    \"characters\": {\n      \"color\": \"#DC322F\"\n    },\n    \"numbers\": {\n      \"color\": \"#DC322F\"\n    },\n    \"keywords\": {\n      \"color\": \"#859900\",\n      \"bold\": true\n    },\n    \"attributes\": {\n      \"color\": \"#6C71C4\"\n    },\n    \"types\": {\n      \"color\": \"#268BD2\"\n    },\n    \"variables\": {\n      \"color\": \"#B58900\"\n    },\n    \"commands\": {\n      \"color\": \"#CB4B16\"\n    },\n    \"values\": {\n      \"color\": \"#D33682\"\n    }\n  },\n  \"terminal\": {\n    \"background\": {\n      \"color\": \"#002B36\"\n    },\n    \"selection\": {\n      \"color\": \"#586E75\"\n    },\n    \"cursor\": {\n      \"color\": \"#839496\"\n    },\n    \"text\": {\n      \"color\": \"#839496\"\n    },\n    \"boldText\": {\n      \"color\": \"#839496\"\n    },\n    \"black\": {\n      \"color\": \"#073642\"\n    },\n    \"red\": {\n      \"color\": \"#DC322F\"\n    },\n    \"green\": {\n      \"color\": \"#859900\"\n    },\n    \"yellow\": {\n      \"color\": \"#B58900\"\n    },\n    \"blue\": {\n      \"color\": \"#268BD2\"\n    },\n    \"magenta\": {\n      \"color\": \"#D33682\"\n    },\n    \"cyan\": {\n      \"color\": \"#2AA198\"\n    },\n    \"white\": {\n      \"color\": \"#EEE8D5\"\n    },\n    \"brightBlack\": {\n      \"color\": \"#002B36\"\n    },\n    \"brightRed\": {\n      \"color\": \"#CB4B16\"\n    },\n    \"brightGreen\": {\n      \"color\": \"#586E75\"\n    },\n    \"brightYellow\": {\n      \"color\": \"#657B83\"\n    },\n    \"brightBlue\": {\n      \"color\": \"#839496\"\n    },\n    \"brightMagenta\": {\n      \"color\": \"#6C71C4\"\n    },\n    \"brightCyan\": {\n      \"color\": \"#93A1A1\"\n    },\n    \"brightWhite\": {\n      \"color\": \"#FDF6E3\"\n    }\n  }\n}\n"
  },
  {
    "path": "DefaultThemes/Solarized (Light).cetheme",
    "content": "{\n  \"name\": \"solarized.light\",\n  \"displayName\": \"Solarized (Light)\",\n  \"description\": \"CodeEdit bundled theme.\",\n  \"author\": \"CodeEdit\",\n  \"version\": \"0.0.1\",\n  \"license\": \"MIT\",\n  \"distributionURL\": \"https://github.com/CodeEditApp/CodeEdit\",\n  \"type\": \"light\",\n  \"editor\": {\n    \"background\": {\n      \"color\": \"#FDF6E3\"\n    },\n    \"selection\": {\n      \"color\": \"#93A1A1\"\n    },\n    \"insertionPoint\": {\n      \"color\": \"#657B83\"\n    },\n    \"lineHighlight\": {\n      \"color\": \"#EEE8D5\"\n    },\n    \"invisibles\": {\n      \"color\": \"#EEE8D5\"\n    },\n    \"text\": {\n      \"color\": \"#657B83\"\n    },\n    \"comments\": {\n      \"color\": \"#93A1A1\"\n    },\n    \"strings\": {\n      \"color\": \"#2AA198\"\n    },\n    \"characters\": {\n      \"color\": \"#DC322F\"\n    },\n    \"numbers\": {\n      \"color\": \"#DC322F\"\n    },\n    \"keywords\": {\n      \"color\": \"#859900\",\n      \"bold\": true\n    },\n    \"attributes\": {\n      \"color\": \"#6C71C4\"\n    },\n    \"types\": {\n      \"color\": \"#268BD2\"\n    },\n    \"variables\": {\n      \"color\": \"#B58900\"\n    },\n    \"commands\": {\n      \"color\": \"#CB4B16\"\n    },\n    \"values\": {\n      \"color\": \"#D33682\"\n    }\n  },\n  \"terminal\": {\n    \"background\": {\n      \"color\": \"#FDF6E3\"\n    },\n    \"selection\": {\n      \"color\": \"#93A1A1\"\n    },\n    \"cursor\": {\n      \"color\": \"#657B83\"\n    },\n    \"text\": {\n      \"color\": \"#657B83\"\n    },\n    \"boldText\": {\n      \"color\": \"#586E75\"\n    },\n    \"black\": {\n      \"color\": \"#073642\"\n    },\n    \"red\": {\n      \"color\": \"#DC322F\"\n    },\n    \"green\": {\n      \"color\": \"#859900\"\n    },\n    \"yellow\": {\n      \"color\": \"#B58900\"\n    },\n    \"blue\": {\n      \"color\": \"#268BD2\"\n    },\n    \"magenta\": {\n      \"color\": \"#D33682\"\n    },\n    \"cyan\": {\n      \"color\": \"#2AA198\"\n    },\n    \"white\": {\n      \"color\": \"#EEE8D5\"\n    },\n    \"brightBlack\": {\n      \"color\": \"#002B36\"\n    },\n    \"brightRed\": {\n      \"color\": \"#CB4B16\"\n    },\n    \"brightGreen\": {\n      \"color\": \"#586E75\"\n    },\n    \"brightYellow\": {\n      \"color\": \"#657B83\"\n    },\n    \"brightBlue\": {\n      \"color\": \"#839496\"\n    },\n    \"brightMagenta\": {\n      \"color\": \"#6C71C4\"\n    },\n    \"brightCyan\": {\n      \"color\": \"#93A1A1\"\n    },\n    \"brightWhite\": {\n      \"color\": \"#FDF6E3\"\n    }\n  }\n}\n"
  },
  {
    "path": "DefaultThemes/Sunset.cetheme",
    "content": "{\n  \"name\": \"sunset\",\n  \"displayName\": \"Sunset\",\n  \"description\": \"CodeEdit bundled theme.\",\n  \"author\": \"CodeEdit\",\n  \"version\": \"0.0.1\",\n  \"license\": \"MIT\",\n  \"distributionURL\": \"https://github.com/CodeEditApp/CodeEdit\",\n  \"type\": \"light\",\n  \"editor\": {\n    \"background\": {\n      \"color\": \"#FFFCEA\"\n    },\n    \"selection\": {\n      \"color\": \"#FBE4AC\"\n    },\n    \"insertionPoint\": {\n      \"color\": \"#000000\"\n    },\n    \"lineHighlight\": {\n      \"color\": \"#FEF6DB\"\n    },\n    \"invisibles\": {\n      \"color\": \"#DACFB3\"\n    },\n    \"text\": {\n      \"color\": \"#000000\"\n    },\n    \"comments\": {\n      \"color\": \"#CF8724\"\n    },\n    \"strings\": {\n      \"color\": \"#E82300\"\n    },\n    \"characters\": {\n      \"color\": \"#35568A\"\n    },\n    \"numbers\": {\n      \"color\": \"#35568A\"\n    },\n    \"keywords\": {\n      \"color\": \"#35568A\",\n      \"bold\": true\n    },\n    \"attributes\": {\n      \"color\": \"#3A48AD\"\n    },\n    \"types\": {\n      \"color\": \"#02638C\"\n    },\n    \"variables\": {\n      \"color\": \"#057CB0\"\n    },\n    \"commands\": {\n      \"color\": \"#587EA8\"\n    },\n    \"values\": {\n      \"color\": \"#587EA8\"\n    }\n  },\n  \"terminal\": {\n    \"background\": {\n      \"color\": \"#FFFCEA\"\n    },\n    \"selection\": {\n      \"color\": \"#FBE4AC\"\n    },\n    \"cursor\": {\n      \"color\": \"#000000\"\n    },\n    \"text\": {\n      \"color\": \"#000000\"\n    },\n    \"boldText\": {\n      \"color\": \"#000000\"\n    },\n    \"black\": {\n      \"color\": \"#000000\"\n    },\n    \"red\": {\n      \"color\": \"#E82300\"\n    },\n    \"green\": {\n      \"color\": \"#02638C\"\n    },\n    \"yellow\": {\n      \"color\": \"#CF8724\"\n    },\n    \"blue\": {\n      \"color\": \"#35568A\"\n    },\n    \"magenta\": {\n      \"color\": \"#3A48AD\"\n    },\n    \"cyan\": {\n      \"color\": \"#587EA8\"\n    },\n    \"white\": {\n      \"color\": \"#FFFFFF\"\n    },\n    \"brightBlack\": {\n      \"color\": \"#000000\"\n    },\n    \"brightRed\": {\n      \"color\": \"#E82300\"\n    },\n    \"brightGreen\": {\n      \"color\": \"#02638C\"\n    },\n    \"brightYellow\": {\n      \"color\": \"#CF8724\"\n    },\n    \"brightBlue\": {\n      \"color\": \"#35568A\"\n    },\n    \"brightMagenta\": {\n      \"color\": \"#3A48AD\"\n    },\n    \"brightCyan\": {\n      \"color\": \"#587EA8\"\n    },\n    \"brightWhite\": {\n      \"color\": \"#FFFFFF\"\n    }\n  }\n}\n"
  },
  {
    "path": "Documentation.docc/About/About Window.md",
    "content": "# About Window\n\nThe About Window displays general information about the app and acknowledgements to external dependencies.\n\n## Topics\n\n### About View\n\n- ``AboutView``\n- ``AboutWindow``\n\n### Acknowledgements\n\n- ``AcknowledgementsView``\n- ``AcknowledgementsViewModel``\n- ``AcknowledgementsViewWindowController``\n- ``AcknowledgementRowView``\n\n- ``AcknowledgementObject``\n- ``AcknowledgementDependency``\n- ``AcknowledgementPackageState``\n- ``AcknowledgementPin``\n"
  },
  {
    "path": "Documentation.docc/App Window/Adding New Tab Type.md",
    "content": "# Adding a New Tab Type\n\nThis article is about how to add a new tab type to `TabBar`\n\n## Overview\n\nFirst of all, each data type to be represented as tab in the UI should conform to\n``EditorTabRepresentable`` protocol. For example, this is how it is done for\n`FileItem`:\n\n```swift\nfinal class FileItem: Identifiable, Codable, EditorTabRepresentable {\n    public var tabID: EditorTabID {\n        .codeEditor(id)\n    }\n\n    public var title: String {\n        self.url.lastPathComponent\n    }\n\n    public var icon: Image {\n        Image(systemName: self.systemImage)\n    }\n\n    public var iconColor: Color {\n        ...\n    }\n\n    ...\n}\n```\n\n### Add new item identifier case\n\nEach new tab type must have new identifier case, for example:\n```swift\npublic enum EditorTabID: Codable, Identifiable, Hashable {\n    public var id: String {\n        switch self {\n        ...\n        case .gitHistory(let repo):\n            return \"gitHistory_\\(repo)\"\n        }\n    }\n\n    /// Represents Git history\n    case gitHistory(String)\n}\n```\n\n### Opening and closing new tab types\n\nTabs are opened using ``WorkspaceDocument/openTab(item:)`` method. It does a set of common\nthings for all tabs. But also it calls a private method based on the ``EditorTabID`` of the\nitem. The private method for your ``EditorTabRepresentable`` MUST persist this item\nsomewhere (I recommend persisting them in ``WorkspaceSelectionState``).\n\nThe same is for closing tabs using ``WorkspaceDocument/closeTab(item:)`` method.\nClosing multiple tabs at once is handled by common functions, so there are no changes\nrequired for them.\n\n``WorkspaceDocument/close()`` calls `WorkspaceDocument.saveSelectionState()` to persist Workspace Selection State to UserDefaults.\n\n``WorkspaceDocument/read(from:ofType:)`` calls `WorkspaceDocument.readSelectionState()` to retrieve Workspace Selection State from UserDefaults.\n\nAlso, because previously opened tabs are persisted in UserDefaults,\nthey should be recovered some way later. To recover new tab types you need to add\na case for ``WorkspaceDocument/read(from:ofType:)`` to let it know how to recover your new tab type.\n\nIf you need to persist something as code editor tabs do for files, then you need to add\nfunctionality to persist changes to ``WorkspaceDocument/close()``.\n\nAlso, you need to add a case for new tab type to\n``WorkspaceSelectionState/getItemByTab(id:)``. It will allow to use\n``WorkspaceSelectionState/selected`` property and other features.\n\n### Adding a view for the new tab type\n\nTo add a view for new tab type, you need to add a case for your tab type to\n``WorkspaceView/tabContent``:\n\n```swift\n@ViewBuilder var tabContent: some View {\n    if let tabID = workspace.selectionState.selectedId {\n        switch tabID {\n            ...\n        case .gitHistory:\n            GitHistoryView(windowController: windowController, workspace: workspace)\n        }\n    } else {\n        noEditor\n    }\n}\n```\n"
  },
  {
    "path": "Documentation.docc/App Window/App Window.md",
    "content": "# App Window\n\nA collection of all the views that make up the main app window.\n\n## Topics\n\n### Window Controller\n\n- ``CodeEditWindowController``\n- ``WorkspaceView``\n\n### Main Content\n\n- ``EditorAreaView``\n- ``EditorAreaFileView``\n- ``CodeFileView``\n- ``NonTextFileView``\n- ``AnyFileView``\n- ``LoadingFileView``\n- ``ImageFileView``\n- ``PDFFileView``\n\n### JumpBar\n\n- ``JumpBarView``\n- ``JumpBarComponent``\n- ``JumpBarMenu``\n- ``JumpBarMenuItem``\n\n### Navigator Sidebar\n\n- ``NavigatorSidebarView``\n\n### Inspector Sidebar\n\n- ``InspectorAreaView``\n\n### Status Bar\n\n- ``StatusBarView``\n\n### Tab Bar\n\n- ``TabBarView``\n\n### Terminal Emulator\n\n- ``TerminalEmulatorView``\n"
  },
  {
    "path": "Documentation.docc/App Window/InspectorSidebarView.md",
    "content": "# ``CodeEdit/InspectorAreaView``\n\n## Topics\n\n### Toolbars\n\n- ``InspectorAreaToolbarTop``\n\n### File Inspector\n\n- ``FileInspectorView``\n- ``FileInspectorModel``\n- ``FileLocation``\n- ``IndentUsing``\n- ``LanguageType``\n- ``LineEndings``\n- ``TextEncoding``\n\n### History Inspector\n\n- ``HistoryInspectorView``\n- ``HistoryInspectorModel``\n- ``HistoryInspectorItemView``\n- ``HistoryInspectorNoHistoryView``\n- ``HistoryPopoverView``\n\n### Quick Help Inspector\n\n- ``QuickHelpInspectorView``\n\n### No Selection\n\n- ``NoSelectionInspectorView``\n\n### File List\n\n- ``FileTypeList``\n"
  },
  {
    "path": "Documentation.docc/App Window/NavigatorSidebarView.md",
    "content": "# ``CodeEdit/NavigatorSidebarView``\n\n## Topics\n\n### Toolbars\n\n- ``NavigatorSidebarToolbarTop``\n- ``NavigatorSidebarToolbarBottom``\n\n### Project Navigator\n\n- ``ProjectNavigatorView``\n- ``OutlineView``\n- ``OutlineViewController``\n- ``OutlineMenu``\n- ``OutlineTableViewCell``\n- ``OutlineTableViewCellDelegate``\n\n### Source Control Navigator\n\n- ``SourceControlNavigatorView``\n- ``SourceControlModel``\n- ``SourceControlSearchToolbar``\n- ``SourceControlToolbarBottom``\n- ``SourceControlNavigatorRepositoriesView``\n- ``SourceControlNavigatorChangesView``\n- ``SourceControlNavigatorChangedFileView``\n\n### Find Navigator\n\n- ``FindNavigatorView``\n- ``FindNavigatorSearchBar``\n- ``FindNavigatorModeSelector``\n- ``FindNavigatorResultList``\n- ``FindNavigatorListViewController``\n- ``FindNavigatorListMatchCell``\n\n### Extension Navigator\n\n- ``ExtensionNavigatorView``\n- ``ExtensionNavigatorItemView``\n- ``ExtensionNavigatorData``\n- ``ExtensionInstallationView``\n- ``ExtensionInstallationViewModel``\n"
  },
  {
    "path": "Documentation.docc/App Window/StatusBarView.md",
    "content": "# ``CodeEdit/StatusBarView``\n\n## Topics\n\n### Model\n\n- ``ImageDimensions``\n\n### View Model\n\n- ``StatusBarViewModel``\n\n### View Modifiers\n\n- ``UpdateStatusBarInfo``\n\n### Items\n\n- ``StatusBarMenuStyle``\n- ``StatusBarBreakpointButton``\n- ``StatusBarIndentSelector``\n- ``StatusBarEncodingSelector``\n- ``StatusBarLineEndSelector``\n- ``StatusBarToggleUtilityAreaButton``\n- ``StatusBarCursorPositionLabel``\n"
  },
  {
    "path": "Documentation.docc/App Window/TabBarView.md",
    "content": "# ``CodeEdit/TabBarView``\n\n## Topics\n\n### Articles\n\n- <doc:Adding-New-Tab-Type>\n\n### Model\n\n- ``EditorTabID``\n- ``EditorTabRepresentable``\n\n### Components\n\n- ``EditorTabView``\n- ``TabBarContextMenu``\n- ``EditorTabButtonStyle``\n- ``TabDivider``\n- ``TabBarTopDivider``\n- ``TabBarBottomDivider``\n- ``TabBarAccessoryIcon``\n- ``TabBarXcodeBackground``\n"
  },
  {
    "path": "Documentation.docc/App Window/UtilityAreaView.md",
    "content": "# ``CodeEdit/UtilityAreaView``\n\n## Topics\n\n### Model\n\n- ``UtilityAreaTab``\n\n### View Model\n\n- ``UtilityAreaViewModel``\n- ``UtilityAreaTabViewModel``\n\n### Utility\n\n- ``UtilityAreaTerminal``\n- ``UtilityAreaTerminalTab``\n- ``UtilityAreaDebugView``\n- ``UtilityAreaOutputView``\n\n### Toolbar\n\n- ``UtilityAreaView``\n- ``UtilityAreaSplitTerminalButton``\n- ``UtilityAreaMaximizeButton``\n- ``UtilityAreaClearButton``\n- ``UtilityAreaFilterTextField``\n"
  },
  {
    "path": "Documentation.docc/AppPreferences/AppPreferences.md",
    "content": "# ``CodeEdit/Settings``\n\n## Topics\n\n### Getting Started\n\n- <doc:Getting-Started>\n- <doc:Create-a-View>\n\n### Settings Model\n\n- ``SettingsModel``\n- ``SoftwareUpdater``\n\n### Settings Section Views\n\n- ``GeneralSettingsView``\n- ``ThemeSettingsView``\n- ``TextEditingSettingsView``\n- ``TerminalSettingsView``\n- ``LocationsSettingsView``\n- ``KeybindingsSettingsView``\n- ``AccountSettingsView``\n- ``SourceControlSettingsView``\n- ``SettingsPlaceholderView``\n\n### Helper Views\n\n- ``SettingsContent``\n- ``SettingsSection``\n- ``SettingsColorPicker``\n- ``SettingsToolbar``\n\n### Theme Settings Model\n\n- ``Theme``\n- ``ThemeModel``\n"
  },
  {
    "path": "Documentation.docc/AppPreferences/Create a View.md",
    "content": "# Create a View\n\nNow that you followed the <doc:Getting-Started> guide it's time to create a view.\n\n## Add setting to existing Section\n\nIn our example we added `ourNewOption` in ``Settings/GeneralSettings``.\n\nNow let's take a look at the ``GeneralSettingsView``.\n\n```swift\nimport SwiftUI\n\nstruct GeneralSettingsView: View {\n    @AppSettings(\\.general)\n    var settings\n\n    var body: some View {\n        SettingsForm {\n            Section {\n                appearance\n                fileIconStyle\n                navigatorTabBarPosition\n                inspectorTabBarPosition\n                ...\n            }\n        }\n    }\n}\n```\n\nAs you can see ``SettingsModel`` is already setup and ready to use.\n\nTo add your option toggle below the other options just add something like this:\n\n```swift\nprivate extension GeneralSettingsView {\n    // MARK: - Settings View\n    \n    private var yourOption: some View {\n        Toggle(\"Your text\", isOn: $general.yourNewOption)\n    }\n}\n```\n\nThen add it to `var body: some View`\n\n```swift\nstruct GeneralSettingsView: View {\n    var body: some View {\n        SettingsForm {\n            Section {\n                appearanceSection\n                showIssuesSection\n                fileExtensionsSection\n                // REMOVEME: et cetera\n                yourOptionSection\n            }\n        }\n    }\n}\n```\n\nAnd now you're done!\n\n## Implement a new section\n\n> Tip: Rename YourSection to the section name that you want\n\nTo implement a new section first create a new folder inside the `Pages` folder and name it accordingly.\n\nInside the folder create a new SwiftUI view and name it \"YourSectionSettingsView.swift\".\n\nThen create a new folder inside called `Models` and inside of it create a file named \"YourSectionSettings.swift\"\n\n\n> Tip: The order that pages are arranged in the array is the same as in the settings window, the first array member will be the top item\n```\n\nThen find the file `SettingsPage.swift` and add `YourSection` to the `enum Name` like this:\n\n```swift\nenum Name: String {\n    case general = \"General\"\n    case advanced = \"Advanced\"\n    // et cetera\n    case yourSection = \"YourSection\"\n}\n```\n\nBack in `YourSectionView.swift` implement your option like this:\n\n```swift\nimport SwiftUI\n\nstruct YourSectionSettingsView: View {\n    @AppSettings(\\.yourSection)\n    var yourSection\n\n    var body: some View {\n        SettingsForm {\n            Section {\n                yourToggleSection\n            }\n        }\n    }\n}\n\nprivate extension YourSectionSettingsView {\n    // MARK: - Settings Views\n\n    private var yourToggle: some View {\n        Toggle(\"Your option\", isOn: $yourSection.yourNewOption)\n    }\n}\n```\n\nThere are 3 more steps, almost done.\n\nOpen `ModelNameToSettingName.swift` and add your translated search result:\n\n```swift\nlet translator: [String: String] = [\n    // MARK: - General Settings\n    \"appAppearance\": NSLocalizedString(\"Appearance\", comment: \"\"),\n    \"fileIconStyle\": NSLocalizedString(\"File Icon Style\", comment: \"\"),\n    // etc\n    // MARK: - Your Section\n    \"yourOption\": NSLocalizedString(\"Your Option\", comment: \"Your translation comment\")\n]\n```\n\nNow, open `SettingsView.swift` and add your section to the `populatePages()` method:\n\n```swift\n/// Creates all the neccessary pages\nprivate func populatePages() -> [SettingsPage] {\n    var pages = [SettingsPage]()\n    let settingsData = SettingsData()\n\n    let generalSettings = SettingsPage(.general, baseColor: .gray, icon: .system(\"gear\"))\n    pages = createPageAndSettings(settingsData.general, parent: generalSettings, prePages: pages)\n\n    let accountsSettings = SettingsPage(.accounts, baseColor: .blue, icon: .system(\"at\"))\n    pages = createPageAndSettings(settingsData.accounts, parent: accountsSettings, prePages: pages)\n\n    // etc\n    let yourSectionSettings = SettingsPage(.yourSection, baseColor: /* add color here */, icon: /* add icon */)\n    pages = createPageAndSettings(settingsData.yourSection, parent: yourSectionSettings, prePages: pages)\n\n    return pages\n}\n```\n\n\nWhen you are done, add `YourSectionSettingsView` to `SettingsView.swift`:\n\n```swift\nGroup {\n    switch selectedPage {\n    case .general:\n        GeneralSettingsView().environmentObject(updater)\n    case .yourSection:\n        YourSectionSettingsView()\n    default:\n        Text(\"Implementation Needed\").frame(alignment: .center)\n    }\n}\n```\n"
  },
  {
    "path": "Documentation.docc/AppPreferences/Getting Started.md",
    "content": "# Getting Started\n\nThere are a few things to consider when using the ``Settings``.\n\n## Reading/Writing Values\n\nThe Settings can be accessed from everywhere in the app like this:\n\n```swift\n@AppSettings(\\.settingName)\nvar setting\n```\n\n```swift\nToggle(\"Enable some Feature\", value: $setting)\n```\n\n## Creating a New Preference\n\nWhen implementing a new feature, we might have some options in regards to this new feature we want to show the user in the apps Settings Window.\n\n### Find a Section\n\nThe settings window is structured in different sections. Figure out in which section your new option should appear in.\n\nIf the section is already populated with other options (e.g. ``Settings/GeneralSettings``), just add your new option like this:\n\n```swift\nstruct GeneralSettings: Codable, Hashable {\n\n  // ...\n\n  // This will be your new option. Be sure to provide a default value\n  public var yourNewOption: Bool = true\n\n  public init() {}\n\n  public init(from decoder: Decoder) throws {\n    let container = try decoder.container(keyedBy: CodingKeys.self)\n    // ...\n\n    // Try to decode the value from json.\n    self.yourNewOption = try container.decodeIfPresent(\n      Bool.self, \n      forKey: .yourNewOption\n    ) ?? true // If the key is not present in the json, set the default value\n  }\n}\n```\n\n### Create a Section\n\nIn some cases in early development the section you decided on where to put your option in might not yet have been implemented. In this\ncase you can create a new `struct` inside ``Settings`` like this:\n\n```swift\npublic extension YourNewSection: Codable {\n\n  // This will be your new option. Be sure to provide a default value\n  public var yourNewOption: Bool = true\n\n  public init() {}\n\n  public init(from decoder: Decoder) throws {\n    let container = try decoder.container(keyedBy: CodingKeys.self)\n\n    // Try to decode the value from json.\n    self.yourNewOption = try container.decodeIfPresent(\n      Bool.self, \n      forKey: .yourNewOption\n    ) ?? true // If the key is not present in the json, set the default value\n  }\n}\n```\n\nNow let's add the new section to ``Settings`` like this:\n\n```swift\npublic struct Settings: Codable {\n  // ...\n\n  // Add your new section above the `public init() {}`\n  public var yourNewSection: YourNewSection = .init()\n\n  // ...\n}\n```\n\n## Topics\n\n### Up Next\n\n- <doc:Create-a-View>\n\n### Main Components\n\n- ``Settings``\n- ``SettingsModel``\n"
  },
  {
    "path": "Documentation.docc/AppPreferences/Sections/AccountPreferencesView.md",
    "content": "# ``CodeEdit/AccountSettingsView``\n\n## Topics\n\n### Model\n\n- ``Settings/AccountsSettings``\n- ``SourceControlAccounts``\n- ``SourceControlProvider``\n\n### Views\n\n- ``AccountListItemView``\n- ``AccountSelectionDialog``\n- ``GitAccountItem``\n- ``GitAccountItemView``\n\n### Login Views\n\n- ``GitLabHostedLoginView``\n- ``GitLabLoginView``\n- ``GitHubLoginView``\n- ``GitHubEnterpriseLoginView``\n"
  },
  {
    "path": "Documentation.docc/AppPreferences/Sections/GeneralPreferencesView.md",
    "content": "# ``CodeEdit/GeneralSettingsView``\n\n## Topics\n\n### Model\n\n- ``Settings/GeneralSettings``\n"
  },
  {
    "path": "Documentation.docc/AppPreferences/Sections/KeybindingsPreferencesView.md",
    "content": "# ``CodeEdit/KeybindingsSettingsView``\n\n## Topics\n\n### Model\n\n- ``Settings/KeybindingsSettings``\n"
  },
  {
    "path": "Documentation.docc/AppPreferences/Sections/SourceControlPreferencesView.md",
    "content": "# ``CodeEdit/SourceControlSettingsView``\n\n## Topics\n\n### Model\n\n- ``Settings/SourceControlSettings``\n- ``IgnoredFiles``\n\n### Views\n\n- ``SourceControlGeneralView``\n- ``SourceControlGitView``\n- ``IgnoredFileView``\n"
  },
  {
    "path": "Documentation.docc/AppPreferences/Sections/TerminalPreferencesView.md",
    "content": "# ``CodeEdit/TerminalSettingsView``\n\n## Topics\n\n### Model\n\n- ``Settings/TerminalSettings``\n"
  },
  {
    "path": "Documentation.docc/AppPreferences/Sections/TextEditingPreferencesView.md",
    "content": "# ``CodeEdit/TextEditingSettingsView``\n\n## Topics\n\n### Model\n\n- ``Settings/TextEditingSettings``\n"
  },
  {
    "path": "Documentation.docc/AppPreferences/Sections/ThemePreferencesView.md",
    "content": "# ``CodeEdit/ThemeSettingsView``\n\n## Topics\n\n### Model\n\n- ``Settings/ThemeSettings``\n- ``ThemeModel``\n\n### Views\n\n- ``PreviewThemeView``\n- ``TerminalThemeView``\n- ``EditorThemeView``\n- ``ThemePreviewIcon``\n"
  },
  {
    "path": "Documentation.docc/AppPreferences/Themes.md",
    "content": "# ``CodeEdit/Theme``\n\n## Overview\n\nA ``Theme`` is stored in a `theme_name.json` file in the `~/Library/Application Support/CodeEdit/themes/` directory. There are a\ncouple of bundled themes that will automatically be put there once the app starts.\n\nOnce a `JSON` file is loaded, the ``Theme`` gets added to ``ThemeModel/themes``.\n\nThey can either be ``Theme/ThemeType/dark`` or ``Theme/ThemeType/light``.\n\n## JSON Structure\n\n```json\n{\n  \"author\" : \"CodeEdit\",\n  \"name\" : \"codeedit-xcode-dark\",\n  \"displayName\" : \"Xcode Dark\",\n  \"description\" : \"Xcode dark theme.\",\n  \"version\" : \"0.0.1\",\n  \"license\" : \"MIT\",\n  \"type\" : \"dark\",\n  \"distributionURL\" : \"https:\\/\\/github.com\\/CodeEditApp\\/CodeEdit\",\n  \"editor\" : { ... },\n  \"terminal\" : { ... }\n}\n```\n\n| Key | Description |\n| --- | ----------- |\n| ``author`` | Your Name |\n| ``name`` | A unique string representing the theme. _It's good practice to start it with your name or domain to make sure it is unique._ |\n| ``displayName`` | The name that will appear in the UI |\n| description / ``metadataDescription`` | A short description that will appear when hovering over the theme thumbnail |\n| ``version`` | A version number |\n| ``license`` | Which license your theme is published under |\n| type / ``appearance`` | The type of the theme [**dark**, **light**] |\n| ``distributionURL`` | A URL to your web presentation |\n| ``editor`` | A collection of colors for the editor |\n| ``terminal`` | A collection of colors for the terminal |\n\n### Editor\n\n```json\n{\n  \"invisibles\" : {\n    \"color\" : \"#424D5B\"\n  },\n  \"comments\" : {\n    \"color\" : \"#73A74E\"\n  },\n  \"numbers\" : {\n    \"color\" : \"#D0BF69\"\n  },\n  \"commands\" : {\n    \"color\" : \"#67B7A4\"\n  },\n  \"lineHighlight\" : {\n    \"color\" : \"#23252B\"\n  },\n  \"values\" : {\n    \"color\" : \"#A167E6\"\n  },\n  \"background\" : {\n    \"color\" : \"#1F1F24\"\n  },\n  \"keywords\" : {\n    \"color\" : \"#FF7AB3\"\n  },\n  \"text\" : {\n    \"color\" : \"#D9D9D9\"\n  },\n  \"insertionPoint\" : {\n    \"color\" : \"#D9D9D9\"\n  },\n  \"strings\" : {\n    \"color\" : \"#FC6A5D\"\n  },\n  \"selection\" : {\n    \"color\" : \"#515B70\"\n  },\n  \"types\" : {\n    \"color\" : \"#5DD8FF\"\n  },\n  \"variables\" : {\n    \"color\" : \"#41A1C0\"\n  },\n  \"attributes\" : {\n    \"color\" : \"#D0A8FF\"\n  },\n  \"characters\" : {\n    \"color\" : \"#D0BF69\"\n  }\n}\n```\n\n### Terminal\n\n```json\n{\n  \"white\" : {\n    \"color\" : \"#d9d9d9\"\n  },\n  \"brightMagenta\" : {\n    \"color\" : \"#af52de\"\n  },\n  \"brightRed\" : {\n    \"color\" : \"#ff3b30\"\n  },\n  \"blue\" : {\n    \"color\" : \"#007aff\"\n  },\n  \"red\" : {\n    \"color\" : \"#ff3b30\"\n  },\n  \"green\" : {\n    \"color\" : \"#28cd41\"\n  },\n  \"boldText\" : {\n    \"color\" : \"#d9d9d9\"\n  },\n  \"brightGreen\" : {\n    \"color\" : \"#28cd41\"\n  },\n  \"background\" : {\n    \"color\" : \"#1f2024\"\n  },\n  \"cursor\" : {\n    \"color\" : \"#d9d9d9\"\n  },\n  \"selection\" : {\n    \"color\" : \"#515b70\"\n  },\n  \"magenta\" : {\n    \"color\" : \"#af52de\"\n  },\n  \"black\" : {\n    \"color\" : \"#1f2024\"\n  },\n  \"text\" : {\n    \"color\" : \"#d9d9d9\"\n  },\n  \"brightWhite\" : {\n    \"color\" : \"#ffffff\"\n  },\n  \"brightBlue\" : {\n    \"color\" : \"#007aff\"\n  },\n  \"brightYellow\" : {\n    \"color\" : \"#ffff00\"\n  },\n  \"cyan\" : {\n    \"color\" : \"#59adc4\"\n  },\n  \"yellow\" : {\n    \"color\" : \"#ffcc00\"\n  },\n  \"brightCyan\" : {\n    \"color\" : \"#55bef0\"\n  },\n   \"brightBlack\" : {\n    \"color\" : \"#8e8e93\"\n  }\n}\n```\n\n## Topics\n\n### General Info\n\n- ``author``\n- ``name``\n- ``displayName``\n- ``metadataDescription``\n- ``version``\n- ``license``\n- ``appearance``\n- ``distributionURL``\n- ``ThemeType``\n\n### Editor\n\n- ``Theme/EditorColors``\n- ``editor``\n- ``Attributes``\n\n### Terminal\n\n- ``Theme/TerminalColors``\n- ``terminal``\n- ``Attributes``\n"
  },
  {
    "path": "Documentation.docc/CodeEditUI/CodeEditUI.md",
    "content": "# CodeEditUI\n\nA collection of reusable UI elements for `CodeEdit`.\n\n## Overview\n\nThis module contains UI elements that can be reused throughout the `CodeEdit` app.\n\n## Topics\n\n### Controls\n\n- ``ToolbarBranchPicker``\n- ``HelpButton``\n- ``SegmentedControl``\n- ``SettingsTextEditor``\n\n### Other\n\n- ``EffectView``\n- ``OverlayPanel``\n- ``PressActions``\n- ``PanelDivider``\n"
  },
  {
    "path": "Documentation.docc/CodeEditUI/HelpButton.md",
    "content": "# ``CodeEdit/HelpButton``\n\n## Usage\n\n```swift\nHelpButton {\n    // an action to perform on click\n}\n```\n\n## Preview\n\n![Help Button](HelpButton_View.png)\n"
  },
  {
    "path": "Documentation.docc/CodeEditUI/SegmentedControl.md",
    "content": "# ``CodeEdit/SegmentedControl``\n\n## Usage\n\n```swift\n@State var selected: Int = 0\nvar items: [String] = [\"Tab 1\", \"Tab 2\"]\n\nSegmentedControl($selected, options: items)\n```\n\n## Preview\n\n![Segmented Control](SegmentedControl_View.png)\n\n## Topics\n\n### Item\n\n- ``SegmentedControlItem``\n"
  },
  {
    "path": "Documentation.docc/CodeEditUI/ToolbarBranchPicker.md",
    "content": "# ``CodeEdit/ToolbarBranchPicker``\n\n## Overview\n\nWhen the current project is a git repository, this will show the currently \nchecked-out branch as a subtitle. Once a tap is registered, a popup will \nappear displaying the currently checked-out branch and all other local branches.\n\nThis view should be set to the `view` property in a [`NSToolbarItem`](https://developer.apple.com/documentation/appkit/nstoolbaritem).\n\n## Usage\n\nFirst make sure a `WorkspaceDocument` is accessible in the context.\n\n```swift\nvar workspace: WorkspaceDocument?\n```\n\nThen in \n[`toolbar(_:itemForItemIdentifier:willBeInsertedIntoToolbar:)`](https://developer.apple.com/documentation/appkit/nstoolbardelegate/1516985-toolbar), \ncreate a new [`NSToolbarItem`](https://developer.apple.com/documentation/appkit/nstoolbaritem):\n\n```swift\nlet toolbarItem = NSToolbarItem(itemIdentifier: /* Identifier */)\n\n// create a NSHostingView\nlet view = BranchPickerToolbarItem(workspace?.workspaceClient)\nlet hostingView = NSHostingView(rootView: view)\n\n// set the view property of the toolbar item\ntoolbarItem.view = hostingView\n\n// return the toolbar item\nreturn toolbarItem\n```\n\n## Preview\n\n![BranchPicker](BranchPicker_View.png)\n"
  },
  {
    "path": "Documentation.docc/Documentation.md",
    "content": "# ``CodeEdit``\n\n## Topics\n\n### About Window\n\n- <doc:About-Window>\n\n### Settings\n\n- ``Settings``\n\n### App Window\n\n- <doc:App-Window>\n\n### CodeEditExtension\n\n- ``ExtensionManager``\n- ``FolderMonitor``\n\n### CodeEditUI\n\n- <doc:CodeEditUI>\n\n### CodeFile\n\n- ``CodeFileDocument``\n- ``CodeFileError``\n\n### CommandPalette\n\n- ``CommandPaletteView``\n- ``CommandPaletteViewModel``\n\n### Documents\n\n- <doc:FileManagement>\n- ``WorkspaceDocument``\n- ``CEWorkspaceFile``\n- ``CEWorkspaceFileManager``\n- ``CodeFileDocument``\n- ``CodeEditDocumentController``\n\n### Feedback\n\n- ``FeedbackView``\n- ``FeedbackWindowController``\n- ``FeedbackToolbar``\n- ``FeedbackModel``\n- ``FeedbackType``\n- ``FeedbackIssueArea``\n\n### Git\n\n- <doc:Git>\n\n### Keybindings\n\n- ``KeybindingManager``\n- ``CommandManager``\n- ``Command``\n- ``CommandClosureWrapper``\n- ``KeyboardShortcutWrapper``\n\n### LanguageServerProtocol\n\n- ``LSPClient``\n\n### OpenQuickly\n\n- ``OpenQuicklyView``\n- ``OpenQuicklyListItemView``\n- ``OpenQuicklyViewModel``\n- ``OpenQuicklyPreviewView``\n\n### Search\n\n- ``SearchModeModel``\n- ``SearchResultModel``\n- ``SearchResultMatchModel``\n- ``SearchResultLabel``\n\n### Utils\n\n- ``CodeEditKeychain``\n- ``ShellClient``\n- ``FileIcon``\n\n### Welcome\n\n- <doc:Welcome-Window>\n"
  },
  {
    "path": "Documentation.docc/FileManagement/FileManagement.md",
    "content": "# File Management\n\nWorking with files and directories in CodeEdit.\n\n## Overview\n\nCodeEdit manages files using three classes:\n- ``CEWorkspaceFile`` for representing files and other file system objects.\n- ``CEWorkspaceFileManager`` for loading, modifying, and listening to the file system.\n- ``CodeFileDocument`` for loading contents of files for editing.\n"
  },
  {
    "path": "Documentation.docc/Git/Git.md",
    "content": "# Git\n\n## Topics\n\n### Client\n\n- ``GitClient``\n\n### Protocols\n\n- ``GitRouter``\n- ``GitJSONPostRouter``\n- ``GitRouterConfiguration``\n- ``GitURLSession``\n- ``GitURLSessionDataTaskProtocol``\n\n### Structs\n\n- ``GitCommit``\n- ``GitAccountItem``\n- ``GitChangedFile``\n- ``GitHTTPHeader``\n\n### Enums\n\n- ``GitHTTPEncoding``\n- ``GitHTTPMethod``\n- ``GitType``\n- ``GitTime``\n- ``GitURL``\n- ``GitSortDirection``\n- ``GitSortType``\n\n### Views\n\n- ``GitCheckoutBranchView``\n- ``GitCloneView``\n- ``GitHubEnterpriseLoginView``\n- ``GitHubLoginView``\n- ``GitLabHostedLoginView``\n- ``GitLabLoginView``\n\n### GitHub\n\n- ``GitHubFile``\n- ``GitHubGist``\n- ``GitHubIssue``\n- ``GitHubPullRequest``\n- ``GitHubRepositories``\n- ``GitHubUser``\n- ``GitHubAccount``\n- ``GitHubComment``\n- ``GitHubReview``\n- ``GitHubTokenConfiguration``\n- ``GitHubRouter``\n- ``GitHubUserRouter``\n- ``GitHubGistRouter``\n- ``GitHubIssueRouter``\n- ``GitHubOAuthRouter``\n- ``GitHubOAuthConfiguration``\n- ``GitHubOpenness``\n- ``GitHubPreviewHeader``\n- ``GitHubPublicKeyRouter``\n- ``GitHubPullRequestRouter``\n- ``GitHubRepositoryRouter``\n- ``GitHubReviewsRouter``\n\n### GitLab\n\n- ``GitLabAvatarURL``\n- ``GitLabCommit``\n- ``GitLabCommitComment``\n- ``GitLabCommitDiff``\n- ``GitLabCommitStats``\n- ``GitLabCommitStatus``\n- ``GitLabEvent``\n- ``GitLabEventData``\n- ``GitLabGroupAccess``\n- ``GitLabPermissions``\n- ``GitLabProject``\n- ``GitLabProjectAccess``\n- ``GitLabProjectHook``\n- ``GitLabUser``\n- ``GitLabNamespace``\n- ``GitLabEventNote``\n- ``GitLabAccount``\n- ``GitLabTokenConfiguration``\n- ``GitLabOAuthConfiguration``\n- ``GitLabPrivateTokenConfiguration``\n- ``GitLabSort``\n- ``GitLabOrderBy``\n- ``GitLabVisibility``\n- ``GitLabVisibilityLevel``\n- ``GitLabUserRouter``\n- ``GitLabCommitRouter``\n- ``GitLabProjectRouter``\n- ``GitLabOAuthRouter``\n\n### BitBucket\n\n- ``BitBucketEmail``\n- ``BitBucketRepositories``\n- ``BitBucketUser``\n- ``BitBucketAccount``\n- ``BitBucketOAuthConfiguration``\n- ``BitBucketTokenConfiguration``\n- ``BitBucketOAuthRouter``\n- ``BitBucketRepositoryRouter``\n- ``BitBucketTokenRouter``\n- ``BitBucketUserRouter``\n- ``BitbucketPaginatedResponse``\n"
  },
  {
    "path": "Documentation.docc/KeyChain/CodeEditKeychain.md",
    "content": "# ``CodeEdit/CodeEditKeychain``\n\n## Topics\n\n### Articles\n\n- <doc:What-is-Keychain>\n\n### Enumerations\n\n- ``CodeEditKeychainAccessOptions``\n- ``CodeEditKeychainConstants``\n"
  },
  {
    "path": "Documentation.docc/KeyChain/What is Keychain.md",
    "content": "#  What is Keychain?\n\nKeychain is the password management system in macOS, developed by Apple. It was introduced with Mac OS 8.6, and has been included in all subsequent versions of the operating system, now known as macOS. A Keychain can contain various types of data: passwords, private keys, certificates, and secure notes. \n\n## Notice:\nThis build of CodeEditKeychain could change at anytime if bugs or breaking changes are found in the module. \n\n## Usage\n\n### String\n\n```swift\nlet keychain = CodeEditKeychain()\nkeychain.set(\"hello world\", forKey: \"my key\")\nkeychain.get(\"my key\")\n```\n\n### Boolean\n\n```swift\nlet keychain = CodeEditKeychain()\nkeychain.set(true, forKey: \"my key\")\nkeychain.getBool(\"my key\")\n```\n\n### Data\n\n```swift\nlet keychain = CodeEditKeychain()\nkeychain.set(dataObject, forKey: \"my key\")\nkeychain.getData(\"my key\")\n```\n### Removing Keys\n\n```swift\nlet keychain = CodeEditKeychain()\nkeychain.delete(\"my key\")\n```\n\n### Return All Keys\n\n```swift\nlet keychain = CodeEditKeychain()\nkeychain.allKeys // Returns the names of all keys\n```\n\n### Check if operation was successful\n\nOne can verify if `set`, `delete` and `clear` methods finished successfully by checking their return values. Those methods return `true` on success and `false` on error.\n\n```swift\nif keychain.set(\"hello world\", forKey: \"my key\") {\n  // Keychain item is saved successfully\n} else {\n  // Report error\n}\n```\n\n### Setting key prefix\n\nOne can pass a `keyPrefix` argument when initializing a `CodeEditKeychain` object. The string passed in `keyPrefix` argument will be used as a prefix to **all the keys** used in `set`, `get`, `getData` and `delete` methods. Adding a prefix to the keychain keys can be useful in unit tests. This prevents the tests from changing the Keychain keys that are used when the app is launched manually.\n\n```swift\nlet keychain = CodeEditKeychain(keyPrefix: \"myTestKey_\")\nkeychain.set(\"hello world\", forKey: \"hello\") // Value will be stored under \"myTestKey_hello\" key\n```\n"
  },
  {
    "path": "Documentation.docc/Keybindings/KeybindingManager.md",
    "content": "# ``CodeEdit/KeybindingManager``\n\nThis module created in order to put all keybindings into single place in code, so it'd be easy to interact, reuse and change keybindings without going through every class and changing code to use other shortcut. It uses `default_keybindings.json` file to store initial set of keybindings. After app launched all keybindings loaded into memory and can be referenced via ``KeybindingManager/named(with:)`` function.\n\n## Initial setup\n\nIn order to get it working you just need to add `Keybindings` as dependency to your module just like\n```\n.target(\n    name: \"WelcomeModule\",\n    dependencies: [\n        ...other dependencies\n        \"Keybindings\",\n    ])\n```\n\nKeybinding module exists as singleton, so you always can reference Keybindings using `KeybindingManager.shared`\n\n## Topics\n\n\n### Fetching shortcut\n\nIn order to fetch keybinding you need to call following function with string name ``KeybindingManager/named(with:)`` returning you ``KeyboardShortcutWrapper`` which contains ``KeyboardShortcutWrapper/keyboardShortcut`` which can be passed directly to  ``keyboardShortcut``. So the end code would look like `.keyboardShortcut(KeyboardShortcutWrapper.shared.named(with: \"copy\").keyboardShortcut`\n\nIf shortcut wasn't found by name, it will return fallback shortcut which has following keybinding `Shift + ?`\n\n### Adding new shortcut\n\nTo add new shortcut you need first to add new row to `default_keybindings.json` file. Make sure you follow other keybindings format. Also check that there's no other keybindings with same ID,\nbecause we use it to identify keybindings later. Once added - you can refer to `Fetching Shortcut` section. It is possible to add new shortcut in runtime via ``KeybindingManager/addNewShortcut(shortcut:name:)``\n"
  },
  {
    "path": "Documentation.docc/Welcome/Welcome Window.md",
    "content": "# Welcome Window\n\n## Topics\n\n### Views\n\n- ``WelcomeWindowView``\n- ``WelcomeView``\n- ``WelcomeActionView``\n\n- ``RecentProjectsView``\n- ``RecentProjectItem``\n"
  },
  {
    "path": "LICENSE.md",
    "content": "MIT License\n\nCopyright (c) 2022 CodeEdit\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "OpenWithCodeEdit/FinderSync.swift",
    "content": "//\n//  FinderSync.swift\n//  openInCodeEdit\n//\n//  Created by Wesley de Groot on 03/05/2022.\n//\n\n/**\n * For anyone working on this file.\n * print does not output to the console, use NSLog.\n * open \"console.app\" to debug,\n */\n\nimport Cocoa\nimport FinderSync\nimport os.log\n\nclass CEOpenWith: FIFinderSync {\n    let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? \"\", category: \"FinderSync\")\n\n    override init() {\n        super.init()\n        // Add finder sync\n        let finderSync = FIFinderSyncController.default()\n        if let mountedVolumes = FileManager.default.mountedVolumeURLs(\n            includingResourceValuesForKeys: nil,\n            options: [.skipHiddenVolumes]\n        ) {\n            finderSync.directoryURLs = Set<URL>(mountedVolumes)\n        }\n        // Monitor volumes\n        let notificationCenter = NSWorkspace.shared.notificationCenter\n        notificationCenter.addObserver(\n            forName: NSWorkspace.didMountNotification,\n            object: nil,\n            queue: .main\n        ) { notification in\n            if let volumeURL = notification.userInfo?[NSWorkspace.volumeURLUserInfoKey] as? URL {\n                finderSync.directoryURLs.insert(volumeURL)\n            }\n        }\n    }\n\n    /// Open in CodeEdit (menu) action\n    /// - Parameter sender: sender\n    @objc\n    func openInCodeEditAction(_ sender: AnyObject?) {\n        guard let items = FIFinderSyncController.default().selectedItemURLs(),\n              let defaults = UserDefaults.init(suiteName: \"app.codeedit.CodeEdit.shared\") else {\n            return\n        }\n\n        // Make values compatible to ArrayLiteralElement\n        var files = \"\"\n\n        for obj in items {\n            files.append(\"\\(obj.path);\")\n        }\n\n        guard let codeEdit = NSWorkspace.shared.urlForApplication(\n            withBundleIdentifier: \"app.codeedit.CodeEdit\"\n        ) else { return }\n\n        // Add files to open to openInCEFiles.\n        defaults.set(files, forKey: \"openInCEFiles\")\n\n        NSWorkspace.shared.open(\n            [],\n            withApplicationAt: codeEdit,\n            configuration: NSWorkspace.OpenConfiguration()\n        )\n    }\n\n    // MARK: - Menu and toolbar item support\n    override func menu(for menuKind: FIMenuKind) -> NSMenu {\n        guard let defaults = UserDefaults.init(suiteName: \"app.codeedit.CodeEdit.shared\") else {\n            logger.error(\"Unable to load defaults\")\n            return NSMenu(title: \"\")\n        }\n\n        // Register enableOpenInCE (enable Open In CodeEdit\n        defaults.register(defaults: [\"enableOpenInCE\": true])\n\n        let menu = NSMenu(title: \"\")\n        let menuItem = NSMenuItem(\n            title: \"Open in CodeEdit\",\n            action: #selector(openInCodeEditAction(_:)),\n            keyEquivalent: \"\"\n        )\n        menuItem.image = NSImage.init(named: \"icon\")\n\n        let enableOpenInCE = defaults.bool(forKey: \"enableOpenInCE\")\n        logger.info(\"Enable Open In CodeEdit value is \\(enableOpenInCE, privacy: .public)\")\n        if enableOpenInCE {\n            menu.addItem(menuItem)\n        }\n        return menu\n    }\n}\n"
  },
  {
    "path": "OpenWithCodeEdit/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>$(DEVELOPMENT_LANGUAGE)</string>\n\t<key>CFBundleDisplayName</key>\n\t<string>OpenWithCodeEdit</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>0.3.6</string>\n\t<key>CFBundleVersion</key>\n\t<string>47</string>\n\t<key>LSUIElement</key>\n\t<true/>\n\t<key>NSExtension</key>\n\t<dict>\n\t\t<key>NSExtensionAttributes</key>\n\t\t<dict/>\n\t\t<key>NSExtensionPointIdentifier</key>\n\t\t<string>com.apple.FinderSync</string>\n\t\t<key>NSExtensionPrincipalClass</key>\n\t\t<string>$(PRODUCT_MODULE_NAME).CEOpenWith</string>\n\t</dict>\n\t<key>NSHumanReadableCopyright</key>\n\t<string>${CE_COPYRIGHT}</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "OpenWithCodeEdit/Media.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "OpenWithCodeEdit/OpenWithCodeEdit.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>com.apple.security.application-groups</key>\n\t<array>\n\t\t<string>app.codeedit.CodeEdit.shared</string>\n\t\t<string>$(TeamIdentifierPrefix)</string>\n\t</array>\n</dict>\n</plist>\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <img src=\"https://github.com/CodeEditApp/CodeEdit/blob/main/.github/CodeEdit-Icon-128@2x.png?raw=true\" height=\"128\">\n  <h1 align=\"center\">CodeEdit for macOS</h1>\n</p>\n\n\n<p align=\"center\">\n  <a aria-label=\"Follow CodeEdit on X\" href=\"https://x.com/CodeEditApp\" target=\"_blank\">\n    <img alt=\"\" src=\"https://img.shields.io/badge/Follow%20@CodeEditApp-black.svg?style=for-the-badge&logo=X\">\n  </a>\n  <a aria-label=\"Follow CodeEdit on BlueSky\" href=\"https://bsky.app/profile/codeedit.app\" target=\"_blank\">\n    <img alt=\"\" src=\"https://img.shields.io/badge/Follow%20@codeedit.app-black.svg?style=for-the-badge&logo=bluesky\">\n  </a>\n  <a aria-label=\"Join the community on Discord\" href=\"https://discord.gg/vChUXVf9Em\" target=\"_blank\">\n    <img alt=\"\" src=\"https://img.shields.io/badge/Join%20the%20community-black.svg?style=for-the-badge&logo=Discord\">\n  </a>\n</p>\n\nCodeEdit is a code editor built by the community, for the community, written entirely and unapologetically for macOS. Features include syntax highlighting, code completion, project find and replace, snippets, terminal, task running, debugging, git integration, code review, extensions, and more.\n\n<img width=\"1012\" alt=\"github-banner\" src=\"https://user-images.githubusercontent.com/806104/194004176-3143d19f-1ad9-449c-bd41-8c4f9998f44b.png\">\n\n[![GitHub release](https://img.shields.io/github/v/release/CodeEditApp/CodeEdit?color=orange&label=latest%20release&sort=semver&style=flat-square)](https://github.com/CodeEditApp/CodeEdit/releases/latest)\n[![All Contributors](https://img.shields.io/badge/all_contributors-32-orange.svg?style=flat-square)](#contributors-)\n[![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/CodeEditApp/CodeEdit/CI-pre-release.yml?style=flat-square)](https://github.com/CodeEditApp/CodeEdit/actions/workflows/CI-pre-release.yml)\n[![GitHub Repo stars](https://img.shields.io/github/stars/CodeEditApp/CodeEdit?style=flat-square)](https://github.com/CodeEditApp/CodeEdit/stargazers)\n[![GitHub forks](https://img.shields.io/github/forks/CodeEditApp/CodeEdit?style=flat-square)](https://github.com/CodeEditApp/CodeEdit/forks)\n[![Discord Badge](https://img.shields.io/discord/951544472238444645?color=5865F2&label=Discord&logo=discord&logoColor=white&style=flat-square)](https://discord.gg/vChUXVf9Em)\n\n> [!IMPORTANT]\n> CodeEdit is currently in development and not yet recommended for production use, however you can take part in shaping it's future by test-driving [pre-release versions](https://github.com/CodeEditApp/CodeEdit/releases) and [submitting an issue](https://github.com/CodeEditApp/CodeEdit/issues) to let us know what you think.\n\n## Table of Contents\n\n- [Motivation](#motivation)\n- [Mission](#mission)\n- [Community](#community)\n- [Activity](#activity)\n- [Contributing](#contributing)\n- [Contributors](#contributors)\n- [Sponsors](#sponsors)\n- [Backers](#backers)\n- [License](#license)\n\n## Motivation\n\nMost editors in use today rely on Electron or other cross-platform frameworks, limiting their ability to fully utilize system resources. While Xcode provides a native experience, it is specifically designed for projects targeting Apple platforms. \n\nWe think developers working on projects not written for Apple platforms deserve that same macOS-native experience we get with Xcode while unlocking the full potential of the Mac. \n\nThis raised the question \"what if such an editor existed?\", a question that led to the creation of [this concept](https://www.figma.com/proto/qj6raZbQsZpGO0NAVi4qsv/CodeEdit-Concept?node-id=1%3A870), which our project aims to make a reality.\n\n## Mission\n\nIt is our commitment to keep CodeEdit open source and free forever, supported by the community.\n\n<img width=\"1012\" alt=\"TextEdit plus Xcode equals CodeEdit\" src=\"https://github.com/CodeEditApp/CodeEdit/assets/806104/a9379df0-ab26-4ef8-98a9-2e2b4bd8c7b4\">\n\nOur goal is to maintain a lightweight experience, similar to TextEdit, while being able to scale up to a more feature-rich experience, comparable to Xcode, as necessary.\n\nWe strive to remain true to Apple's human interface guidelines and development patterns, ensuring CodeEdit looks and feels like an application developed by Apple themselves, which includes a meticulous attention to detail.\n\n## Community\n\nJoin our growing community on [Discord](https://discord.gg/vChUXVf9Em) and [GitHub Discussions](https://github.com/orgs/CodeEditApp/discussions) where we discuss and collaborate on all things CodeEdit. Don't be shy, jump right in and be part of the discussion!\n\n> [!NOTE]\n> We hold a weekly meetup on Discord **every Saturday at 3pm UTC** where we discuss latests development, feature requests, goals, and priorities.\n> \n> [**Join us**](https://discord.gg/KvdE3wYKNR?event=1189275961456336937)\n> \n## Activity\n\n<img src=\"https://repobeats.axiom.co/api/embed/acd5eeb654fc98f6ffbce26b09fd0e3603edf1fd.svg\" alt=\"CodeEdit Repository Activity\" width=\"100%\" />\n\n## Contributing\n\nBe part of the next revolution in code editing by contributing to the project. This is a community-led effort, so we welcome as many contributors who can help. Read the [Contribution Guide](https://github.com/CodeEditApp/CodeEdit/blob/main/CONTRIBUTING.md) for more information.\n\nThis project spans [multiple repositories](https://github.com/CodeEditApp/CodeEdit#related-repositories) so instead of browsing issues in the issues tab, it may be helpful to find an issue to get started on in our [project board](https://github.com/orgs/CodeEditApp/projects/3/views/1).\n\nFor issues we want to focus on that are most relevant at any given time, please see the issues scoped to our current iteration [here](https://github.com/orgs/CodeEditApp/projects/3/views/10).\n\n## Contributors\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tbody>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://www.austincondiff.com\"><img src=\"https://avatars.githubusercontent.com/u/806104?v=4?s=100\" width=\"100px;\" alt=\"Austin Condiff\"/><br /><sub><b>Austin Condiff</b></sub></a><br /><a href=\"#design-austincondiff\" title=\"Design\">🎨</a> <a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=austincondiff\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://lukaspistrol.com\"><img src=\"https://avatars.githubusercontent.com/u/9460130?v=4?s=100\" width=\"100px;\" alt=\"Lukas Pistrol\"/><br /><sub><b>Lukas Pistrol</b></sub></a><br /><a href=\"#infra-lukepistrol\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=lukepistrol\" title=\"Tests\">⚠️</a> <a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=lukepistrol\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://blog.windchillmedia.com\"><img src=\"https://avatars.githubusercontent.com/u/35942988?v=4?s=100\" width=\"100px;\" alt=\"Khan Winter\"/><br /><sub><b>Khan Winter</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=thecoolwinter\" title=\"Code\">💻</a> <a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3Athecoolwinter\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/matthijseikelenboom\"><img src=\"https://avatars.githubusercontent.com/u/1364843?v=4?s=100\" width=\"100px;\" alt=\"Matthijs Eikelenboom\"/><br /><sub><b>Matthijs Eikelenboom</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=matthijseikelenboom\" title=\"Code\">💻</a> <a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3Amatthijseikelenboom\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/Wouter01\"><img src=\"https://avatars.githubusercontent.com/u/62355975?v=4?s=100\" width=\"100px;\" alt=\"Wouter Hennen\"/><br /><sub><b>Wouter Hennen</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=Wouter01\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://wdg.codes\"><img src=\"https://avatars.githubusercontent.com/u/1290461?v=4?s=100\" width=\"100px;\" alt=\"Wesley De Groot\"/><br /><sub><b>Wesley De Groot</b></sub></a><br /><a href=\"#infra-0xWDG\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=0xWDG\" title=\"Tests\">⚠️</a> <a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=0xWDG\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/KaiTheRedNinja\"><img src=\"https://avatars.githubusercontent.com/u/88234730?v=4?s=100\" width=\"100px;\" alt=\"KaiTheRedNinja\"/><br /><sub><b>KaiTheRedNinja</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=KaiTheRedNinja\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/pkasila\"><img src=\"https://avatars.githubusercontent.com/u/17158860?v=4?s=100\" width=\"100px;\" alt=\"Pavel Kasila\"/><br /><sub><b>Pavel Kasila</b></sub></a><br /><a href=\"#infra-pkasila\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=pkasila\" title=\"Tests\">⚠️</a> <a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=pkasila\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/MarcoCarnevali\"><img src=\"https://avatars.githubusercontent.com/u/9656572?v=4?s=100\" width=\"100px;\" alt=\"Marco Carnevali\"/><br /><sub><b>Marco Carnevali</b></sub></a><br /><a href=\"#infra-MarcoCarnevali\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=MarcoCarnevali\" title=\"Tests\">⚠️</a> <a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=MarcoCarnevali\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/nanashili\"><img src=\"https://avatars.githubusercontent.com/u/63672227?v=4?s=100\" width=\"100px;\" alt=\"Nanashi Li\"/><br /><sub><b>Nanashi Li</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=nanashili\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://ninjiacoder.me\"><img src=\"https://avatars.githubusercontent.com/u/22616933?v=4?s=100\" width=\"100px;\" alt=\"ninjiacoder\"/><br /><sub><b>ninjiacoder</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=RayZhao1998\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://twitch.tv/Jeehut\"><img src=\"https://avatars.githubusercontent.com/u/6942160?v=4?s=100\" width=\"100px;\" alt=\"Cihat Gündüz\"/><br /><sub><b>Cihat Gündüz</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=Jeehut\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/MysteryCoder456\"><img src=\"https://avatars.githubusercontent.com/u/43755491?v=4?s=100\" width=\"100px;\" alt=\"Rehatbir Singh\"/><br /><sub><b>Rehatbir Singh</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=MysteryCoder456\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/Angelk90\"><img src=\"https://avatars.githubusercontent.com/u/20476002?v=4?s=100\" width=\"100px;\" alt=\"Angelk90\"/><br /><sub><b>Angelk90</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=Angelk90\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://www.stefkors.com\"><img src=\"https://avatars.githubusercontent.com/u/11800807?v=4?s=100\" width=\"100px;\" alt=\"Stef Kors\"/><br /><sub><b>Stef Kors</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=StefKors\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://akringblog.com/\"><img src=\"https://avatars.githubusercontent.com/u/6525286?v=4?s=100\" width=\"100px;\" alt=\"Chris Akring\"/><br /><sub><b>Chris Akring</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=akring\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/highjeans\"><img src=\"https://avatars.githubusercontent.com/u/77588045?v=4?s=100\" width=\"100px;\" alt=\"highjeans\"/><br /><sub><b>highjeans</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=highjeans\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/jasonplatts\"><img src=\"https://avatars.githubusercontent.com/u/48892071?v=4?s=100\" width=\"100px;\" alt=\"Jason Platts\"/><br /><sub><b>Jason Platts</b></sub></a><br /><a href=\"#infra-jasonplatts\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"#plugin-jasonplatts\" title=\"Plugin/utility libraries\">🔌</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/dzign1\"><img src=\"https://avatars.githubusercontent.com/u/44317715?v=4?s=100\" width=\"100px;\" alt=\"Rob Hughes\"/><br /><sub><b>Rob Hughes</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=dzign1\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://lingxi.li\"><img src=\"https://avatars.githubusercontent.com/u/36816148?v=4?s=100\" width=\"100px;\" alt=\"Lingxi Li\"/><br /><sub><b>Lingxi Li</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=lilingxi01\" title=\"Code\">💻</a> <a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3Alilingxi01\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/octree\"><img src=\"https://avatars.githubusercontent.com/u/7934444?v=4?s=100\" width=\"100px;\" alt=\"HZ.Liu\"/><br /><sub><b>HZ.Liu</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=octree\" title=\"Code\">💻</a> <a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3Aoctree\" title=\"Bug reports\">🐛</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://www.youtube.com/channel/UCx1gvWpy5zjOd7yZyDwmXEA?sub_confirmation=1\"><img src=\"https://avatars.githubusercontent.com/u/8013017?v=4?s=100\" width=\"100px;\" alt=\"Richard Topchii\"/><br /><sub><b>Richard Topchii</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=richardtop\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/Pythonen\"><img src=\"https://avatars.githubusercontent.com/u/53183345?v=4?s=100\" width=\"100px;\" alt=\"Pythonen\"/><br /><sub><b>Pythonen</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=Pythonen\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/jav-solo\"><img src=\"https://avatars.githubusercontent.com/u/10246220?v=4?s=100\" width=\"100px;\" alt=\"Javier Solorzano\"/><br /><sub><b>Javier Solorzano</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=jav-solo\" title=\"Code\">💻</a> <a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3Ajav-solo\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://angcosmin.com\"><img src=\"https://avatars.githubusercontent.com/u/8146514?v=4?s=100\" width=\"100px;\" alt=\"Cosmin Anghel\"/><br /><sub><b>Cosmin Anghel</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=AngCosmin\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://mmshivesh.ml\"><img src=\"https://avatars.githubusercontent.com/u/23611514?v=4?s=100\" width=\"100px;\" alt=\"Shivesh\"/><br /><sub><b>Shivesh</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=mmshivesh\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/drucelweisse\"><img src=\"https://avatars.githubusercontent.com/u/36012972?v=4?s=100\" width=\"100px;\" alt=\"Andrey Plotnikov\"/><br /><sub><b>Andrey Plotnikov</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=drucelweisse\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/POPOBE97\"><img src=\"https://avatars.githubusercontent.com/u/7891810?v=4?s=100\" width=\"100px;\" alt=\"POPOBE97\"/><br /><sub><b>POPOBE97</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=POPOBE97\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/nrudnyk\"><img src=\"https://avatars.githubusercontent.com/u/20221382?v=4?s=100\" width=\"100px;\" alt=\"nrudnyk\"/><br /><sub><b>nrudnyk</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=nrudnyk\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/benkoska\"><img src=\"https://avatars.githubusercontent.com/u/17319613?v=4?s=100\" width=\"100px;\" alt=\"Ben Koska\"/><br /><sub><b>Ben Koska</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=benkoska\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/evolify\"><img src=\"https://avatars.githubusercontent.com/u/12669069?v=4?s=100\" width=\"100px;\" alt=\"evolify\"/><br /><sub><b>evolify</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3Aevolify\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/shibotong\"><img src=\"https://avatars.githubusercontent.com/u/44807628?v=4?s=100\" width=\"100px;\" alt=\"Shibo Tong\"/><br /><sub><b>Shibo Tong</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=shibotong\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://ethanwong.me\"><img src=\"https://avatars.githubusercontent.com/u/8158163?v=4?s=100\" width=\"100px;\" alt=\"Ethan Wong\"/><br /><sub><b>Ethan Wong</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=GetToSet\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://gantoreno.com\"><img src=\"https://avatars.githubusercontent.com/u/43397475?v=4?s=100\" width=\"100px;\" alt=\"Gabriel Moreno\"/><br /><sub><b>Gabriel Moreno</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3Agantoreno\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/Prince213\"><img src=\"https://avatars.githubusercontent.com/u/25235514?v=4?s=100\" width=\"100px;\" alt=\"Sizhe Zhao\"/><br /><sub><b>Sizhe Zhao</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3APrince213\" title=\"Bug reports\">🐛</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/Muhammed9991\"><img src=\"https://avatars.githubusercontent.com/u/80204376?v=4?s=100\" width=\"100px;\" alt=\"Muhammed Mahmood\"/><br /><sub><b>Muhammed Mahmood</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=Muhammed9991\" title=\"Code\">💻</a> <a href=\"#maintenance-Muhammed9991\" title=\"Maintenance\">🚧</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/muescha\"><img src=\"https://avatars.githubusercontent.com/u/184316?v=4?s=100\" width=\"100px;\" alt=\"Muescha\"/><br /><sub><b>Muescha</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=muescha\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://alexsinelnikov.io/\"><img src=\"https://avatars.githubusercontent.com/u/1757017?v=4?s=100\" width=\"100px;\" alt=\"Alex Sinelnikov\"/><br /><sub><b>Alex Sinelnikov</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=avdept\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://pribess.github.io\"><img src=\"https://avatars.githubusercontent.com/u/72389357?v=4?s=100\" width=\"100px;\" alt=\"Heewon Cho\"/><br /><sub><b>Heewon Cho</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3APribess\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://www.xcodes.app\"><img src=\"https://avatars.githubusercontent.com/u/1119565?v=4?s=100\" width=\"100px;\" alt=\"Matt Kiazyk\"/><br /><sub><b>Matt Kiazyk</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=MattKiazyk\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/DingoBits\"><img src=\"https://avatars.githubusercontent.com/u/107956274?v=4?s=100\" width=\"100px;\" alt=\"DingoBits\"/><br /><sub><b>DingoBits</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=DingoBits\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/sk409\"><img src=\"https://avatars.githubusercontent.com/u/25968819?v=4?s=100\" width=\"100px;\" alt=\"Shoto Kobayashi\"/><br /><sub><b>Shoto Kobayashi</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3Ask409\" title=\"Bug reports\">🐛</a> <a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=sk409\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://www.linkedin.com/in/aaryankotharii\"><img src=\"https://avatars.githubusercontent.com/u/53724307?v=4?s=100\" width=\"100px;\" alt=\"Aaryan Kothari\"/><br /><sub><b>Aaryan Kothari</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3Aaaryankotharii\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://kyleye.top/\"><img src=\"https://avatars.githubusercontent.com/u/43724855?v=4?s=100\" width=\"100px;\" alt=\"Kyle\"/><br /><sub><b>Kyle</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=Kyle-Ye\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/NakaokaRei\"><img src=\"https://avatars.githubusercontent.com/u/39183069?v=4?s=100\" width=\"100px;\" alt=\"Nakaoka Rei\"/><br /><sub><b>Nakaoka Rei</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=NakaokaRei\" title=\"Code\">💻</a> <a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3ANakaokaRei\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/alexdeem\"><img src=\"https://avatars.githubusercontent.com/u/404584?v=4?s=100\" width=\"100px;\" alt=\"Alex Deem\"/><br /><sub><b>Alex Deem</b></sub></a><br /><a href=\"#maintenance-alexdeem\" title=\"Maintenance\">🚧</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/denizak\"><img src=\"https://avatars.githubusercontent.com/u/1758456?v=4?s=100\" width=\"100px;\" alt=\"deni zakya\"/><br /><sub><b>deni zakya</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3Adenizak\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/ahmdyasser\"><img src=\"https://avatars.githubusercontent.com/u/42544598?v=4?s=100\" width=\"100px;\" alt=\"Ahmad Yasser\"/><br /><sub><b>Ahmad Yasser</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3Aahmdyasser\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/ezraberch\"><img src=\"https://avatars.githubusercontent.com/u/49635435?v=4?s=100\" width=\"100px;\" alt=\"ezraberch\"/><br /><sub><b>ezraberch</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=ezraberch\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/Eliulm\"><img src=\"https://avatars.githubusercontent.com/u/82230675?v=4?s=100\" width=\"100px;\" alt=\"Elias Wahl\"/><br /><sub><b>Elias Wahl</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3AEliulm\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/bombardier200\"><img src=\"https://avatars.githubusercontent.com/u/25121427?v=4?s=100\" width=\"100px;\" alt=\"bombardier200\"/><br /><sub><b>bombardier200</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=bombardier200\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/yapryntsev\"><img src=\"https://avatars.githubusercontent.com/u/18378212?v=4?s=100\" width=\"100px;\" alt=\"Alex Yapryntsev\"/><br /><sub><b>Alex Yapryntsev</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=yapryntsev\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/Code-DJ\"><img src=\"https://avatars.githubusercontent.com/u/8212554?v=4?s=100\" width=\"100px;\" alt=\"Code-DJ\"/><br /><sub><b>Code-DJ</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=Code-DJ\" title=\"Code\">💻</a> <a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3ACode-DJ\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/neilZon\"><img src=\"https://avatars.githubusercontent.com/u/46465568?v=4?s=100\" width=\"100px;\" alt=\"Neilzon Viloria\"/><br /><sub><b>Neilzon Viloria</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3AneilZon\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://cubik65536.top\"><img src=\"https://avatars.githubusercontent.com/u/72877496?v=4?s=100\" width=\"100px;\" alt=\"Cubik\"/><br /><sub><b>Cubik</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3ACubik65536\" title=\"Bug reports\">🐛</a> <a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=Cubik65536\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://twitter.com/RenanGreca\"><img src=\"https://avatars.githubusercontent.com/u/5760386?v=4?s=100\" width=\"100px;\" alt=\"Renan Greca\"/><br /><sub><b>Renan Greca</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=RenanGreca\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/maxkel\"><img src=\"https://avatars.githubusercontent.com/u/46418077?v=4?s=100\" width=\"100px;\" alt=\"maxkel\"/><br /><sub><b>maxkel</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3Amaxkel\" title=\"Bug reports\">🐛</a> <a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=maxkel\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://scrapp08.xyz\"><img src=\"https://avatars.githubusercontent.com/u/105889363?v=4?s=100\" width=\"100px;\" alt=\"Scrap\"/><br /><sub><b>Scrap</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=scrapp08\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/iggy890\"><img src=\"https://avatars.githubusercontent.com/u/98705626?v=4?s=100\" width=\"100px;\" alt=\"iggy890\"/><br /><sub><b>iggy890</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=iggy890\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/stavares843\"><img src=\"https://avatars.githubusercontent.com/u/29093946?v=4?s=100\" width=\"100px;\" alt=\"Sara Tavares\"/><br /><sub><b>Sara Tavares</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3Astavares843\" title=\"Bug reports\">🐛</a> <a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=stavares843\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/luah5\"><img src=\"https://avatars.githubusercontent.com/u/128280019?v=4?s=100\" width=\"100px;\" alt=\"luah5\"/><br /><sub><b>luah5</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=luah5\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/evanlwang\"><img src=\"https://avatars.githubusercontent.com/u/71157264?v=4?s=100\" width=\"100px;\" alt=\"Evan Wang\"/><br /><sub><b>Evan Wang</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=evanlwang\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/dscyrescotti\"><img src=\"https://avatars.githubusercontent.com/u/67727096?v=4?s=100\" width=\"100px;\" alt=\"Dscyre Scotti\"/><br /><sub><b>Dscyre Scotti</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=dscyrescotti\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://tomasboda.com\"><img src=\"https://avatars.githubusercontent.com/u/40064599?v=4?s=100\" width=\"100px;\" alt=\"Tomáš Boďa\"/><br /><sub><b>Tomáš Boďa</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3Advandyy\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/Ahattalla\"><img src=\"https://avatars.githubusercontent.com/u/53402452?v=4?s=100\" width=\"100px;\" alt=\"Ahmed Attalla\"/><br /><sub><b>Ahmed Attalla</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3AAhattalla\" title=\"Bug reports\">🐛</a> <a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=Ahattalla\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://estebanborai.com\"><img src=\"https://avatars.githubusercontent.com/u/34756077?v=4?s=100\" width=\"100px;\" alt=\"Esteban Borai\"/><br /><sub><b>Esteban Borai</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=EstebanBorai\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/avinizhanov\"><img src=\"https://avatars.githubusercontent.com/u/42622715?v=4?s=100\" width=\"100px;\" alt=\"avinizhanov\"/><br /><sub><b>avinizhanov</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3Aavinizhanov\" title=\"Bug reports\">🐛</a> <a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=avinizhanov\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/kmohsin11\"><img src=\"https://avatars.githubusercontent.com/u/28269317?v=4?s=100\" width=\"100px;\" alt=\"kmohsin11\"/><br /><sub><b>kmohsin11</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3Akmohsin11\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/armartinez\"><img src=\"https://avatars.githubusercontent.com/u/1909987?v=4?s=100\" width=\"100px;\" alt=\"Axel Martinez\"/><br /><sub><b>Axel Martinez</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3Aarmartinez\" title=\"Bug reports\">🐛</a> <a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=armartinez\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://nivora.app\"><img src=\"https://avatars.githubusercontent.com/u/5382443?v=4?s=100\" width=\"100px;\" alt=\"Federico Zivolo\"/><br /><sub><b>Federico Zivolo</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=FezVrasta\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/ElvisWong213\"><img src=\"https://avatars.githubusercontent.com/u/40566101?v=4?s=100\" width=\"100px;\" alt=\"Elvis Wong\"/><br /><sub><b>Elvis Wong</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3AElvisWong213\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://ibrahimcetin.dev\"><img src=\"https://avatars.githubusercontent.com/u/33904390?v=4?s=100\" width=\"100px;\" alt=\"İbrahim Çetin\"/><br /><sub><b>İbrahim Çetin</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3Aibrahimcetin\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/phlpsong\"><img src=\"https://avatars.githubusercontent.com/u/103433299?v=4?s=100\" width=\"100px;\" alt=\"phlpsong\"/><br /><sub><b>phlpsong</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3Aphlpsong\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://ahnafmahmud.com\"><img src=\"https://avatars.githubusercontent.com/u/44692189?v=4?s=100\" width=\"100px;\" alt=\"Ahnaf Mahmud\"/><br /><sub><b>Ahnaf Mahmud</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=infinitepower18\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/DanKlaver15\"><img src=\"https://avatars.githubusercontent.com/u/9391497?v=4?s=100\" width=\"100px;\" alt=\"Dan K\"/><br /><sub><b>Dan K</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=DanKlaver15\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://knotbin.xyz\"><img src=\"https://avatars.githubusercontent.com/u/118622417?v=4?s=100\" width=\"100px;\" alt=\"Roscoe Rubin-Rottenberg\"/><br /><sub><b>Roscoe Rubin-Rottenberg</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=knotbin\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/plbstl\"><img src=\"https://avatars.githubusercontent.com/u/49006567?v=4?s=100\" width=\"100px;\" alt=\"Paul Ebose\"/><br /><sub><b>Paul Ebose</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3Aplbstl\" title=\"Bug reports\">🐛</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://danielz.xyz\"><img src=\"https://avatars.githubusercontent.com/u/65467530?v=4?s=100\" width=\"100px;\" alt=\"Daniel Zhu\"/><br /><sub><b>Daniel Zhu</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3Adanielzsh\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/simonwhitaker\"><img src=\"https://avatars.githubusercontent.com/u/116432?v=4?s=100\" width=\"100px;\" alt=\"Simon Whitaker\"/><br /><sub><b>Simon Whitaker</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3Asimonwhitaker\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/LeonardoLarranaga\"><img src=\"https://avatars.githubusercontent.com/u/83844690?v=4?s=100\" width=\"100px;\" alt=\"Leonardo\"/><br /><sub><b>Leonardo</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=LeonardoLarranaga\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/scaredcr6w\"><img src=\"https://avatars.githubusercontent.com/u/85457088?v=4?s=100\" width=\"100px;\" alt=\"Levente Anda\"/><br /><sub><b>Levente Anda</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=scaredcr6w\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://nobelliu.github.com\"><img src=\"https://avatars.githubusercontent.com/u/10796646?v=4?s=100\" width=\"100px;\" alt=\"Nobel\"/><br /><sub><b>Nobel</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=NobelLiu\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/SavelyUkuren\"><img src=\"https://avatars.githubusercontent.com/u/125015568?v=4?s=100\" width=\"100px;\" alt=\"Savely\"/><br /><sub><b>Savely</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=SavelyUkuren\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/Kihron\"><img src=\"https://avatars.githubusercontent.com/u/30128800?v=4?s=100\" width=\"100px;\" alt=\"Kihron\"/><br /><sub><b>Kihron</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3AKihron\" title=\"Bug reports\">🐛</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/pro100filipp\"><img src=\"https://avatars.githubusercontent.com/u/12880697?v=4?s=100\" width=\"100px;\" alt=\"Filipp Kuznetsov\"/><br /><sub><b>Filipp Kuznetsov</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=pro100filipp\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/rustemd02\"><img src=\"https://avatars.githubusercontent.com/u/11714456?v=4?s=100\" width=\"100px;\" alt=\"rustemd02\"/><br /><sub><b>rustemd02</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/issues?q=author%3Arustemd02\" title=\"Bug reports\">🐛</a> <a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=rustemd02\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/SimonKudsk\"><img src=\"https://avatars.githubusercontent.com/u/10168417?v=4?s=100\" width=\"100px;\" alt=\"Simon Kudsk\"/><br /><sub><b>Simon Kudsk</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=SimonKudsk\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/Syrux64\"><img src=\"https://avatars.githubusercontent.com/u/118998822?v=4?s=100\" width=\"100px;\" alt=\"Surya\"/><br /><sub><b>Surya</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=Syrux64\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/georgetchelidze\"><img src=\"https://avatars.githubusercontent.com/u/96194129?v=4?s=100\" width=\"100px;\" alt=\"George Tchelidze\"/><br /><sub><b>George Tchelidze</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=georgetchelidze\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://zhr.is\"><img src=\"https://avatars.githubusercontent.com/u/148818634?v=4?s=100\" width=\"100px;\" alt=\"Chris Pineda\"/><br /><sub><b>Chris Pineda</b></sub></a><br /><a href=\"https://github.com/CodeEditApp/CodeEdit/commits?author=zhrispineda\" title=\"Code\">💻</a></td>\n    </tr>\n  </tbody>\n</table>\n\n<!-- markdownlint-restore -->\n<!-- prettier-ignore-end -->\n\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\n## Sponsors\n\nSupport CodeEdit's development by [becoming a sponsor](https://github.com/sponsors/CodeEditApp).\n\n<a title=\"Vercel\" href=\"https://vercel.com/?utm_source=codeedit&utm_campaign=oss\" target=\"_blank\"><img src=\"https://user-images.githubusercontent.com/806104/162766170-60f3b95a-ca30-4015-a3e3-a605df78b98a.png\" width=\"128\"></a>\n<a title=\"MacStadium\" href=\"https://macstadium.com\" target=\"_blank\"><img src=\"https://user-images.githubusercontent.com/806104/162766594-eff7f985-31a9-48c5-9e58-139794fefa10.png\" width=\"128\"></a>\n<a title=\"GitBook\" href=\"https://www.gitbook.com/\" target=\"_blank\"><img src=\"https://user-images.githubusercontent.com/806104/162766464-c10dc9fc-088a-4945-a0e1-17bd42705b70.png\" width=\"128\"></a>\n<a title=\"panascais\" href=\"https://github.com/panascais\" target=\"_blank\"><img src=\"https://avatars.githubusercontent.com/u/19628635?s=200&v=4\" width=\"128\"></a>\n<a title=\"DevUtilsApp\" href=\"https://devutils.app/?utm_source=codeedit&utm_campaign=oss\" target=\"_blank\"><img src=\"https://devutils.app/512.png\" width=\"128\"></a>\n<a title=\"Proxyman\" href=\"https://proxyman.io/\" target=\"_blank\"><img src=\"https://user-images.githubusercontent.com/35942988/215859653-0d3622e4-a214-4691-acbe-cd96ca833193.png\" width=\"128\"></a>\n<a title=\"TablePlus\" href=\"https://tableplus.com/\" target=\"_blank\"><img src=\"https://avatars.githubusercontent.com/u/29408238?s=200&v=4\" width=\"128\"></a>\n\n## Backers\n\nSupport CodeEdit's development by [becoming a backer](https://github.com/sponsors/CodeEditApp).\n\n<a title=\"dannydorazio\" href=\"https://github.com/dannydorazio\" target=\"_blank\"><img src=\"https://avatars.githubusercontent.com/u/21158275?v=4\" width=\"64\"></a>\n<a title=\"omrd\" href=\"https://github.com/omrd\" target=\"_blank\"><img src=\"https://avatars.githubusercontent.com/u/34616424?v=4\" width=\"64\"></a>\n<a title=\"sparrowcode\" href=\"https://github.com/sparrowcode\" target=\"_blank\"><img src=\"https://avatars.githubusercontent.com/u/98487302?s=200&v=4\" width=\"64\"></a>\n<a title=\"Gebes\" href=\"https://github.com/Gebes\" target=\"_blank\"><img src=\"https://avatars.githubusercontent.com/u/35232234?v=4\" width=\"64\"></a>\n<a title=\"lovetodream\" href=\"https://github.com/lovetodream\" target=\"_blank\"><img src=\"https://avatars.githubusercontent.com/u/38291523?v=4\" width=\"64\"></a>\n<a title=\"ridafkih\" href=\"https://github.com/ridafkih\" target=\"_blank\"><img src=\"https://avatars.githubusercontent.com/u/9158485?v=4\" width=\"64\"></a>\n<a title=\"tjkohi\" href=\"https://github.com/tjkohli\" target=\"_blank\"><img src=\"https://avatars.githubusercontent.com/u/7361247?v=4\" width=\"64\"></a>\n<a title=\"mattpilott\" href=\"https://github.com/mattpilott\" target=\"_blank\"><img src=\"https://avatars.githubusercontent.com/u/2401925?v=4\" width=\"64\"></a>\n\n\n### Thanks to all of our other backers\n\n[@ivanvorobei](https://github.com/ivanvorobei)\n[@albertorestifo](https://github.com/albertorestifo)\n[@rkusa](https://github.com/rkusa)\n[@cadenkriese](https://github.com/cadenkriese)\n[@petrjahoda](https://github.com/petrjahoda)\n[@allejo](https://github.com/allejo)\n[@frousselet](frousselet)\n[@wkillerud](wkillerud)\n\n## License\n\nLicensed under the [MIT license](https://github.com/CodeEditApp/CodeEdit/blob/main/LICENSE.md).\n\n## Related Repositories\n\n<table>\n  <tr>\n    <td align=\"center\">\n      <a href=\"https://github.com/CodeEditApp/CodeEditKit\">\n        <img src=\"https://github.com/CodeEditApp/CodeEditKit/blob/main/.github/CodeEditKit-Icon-128@2x.png?raw=true\" height=\"128\">\n      </a>\n      <p>&nbsp;&nbsp;&nbsp;&nbsp;<a href=\"https://github.com/CodeEditApp/CodeEditKit\">CodeEditKit</a>&nbsp;&nbsp;&nbsp;&nbsp;</p>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://github.com/CodeEditApp/CodeEditTextView\">\n        <img src=\"https://github.com/CodeEditApp/CodeEditTextView/blob/main/.github/CodeEditTextView-Icon-128@2x.png?raw=true\" height=\"128\">\n      </a>\n      <p><a href=\"https://github.com/CodeEditApp/CodeEditTextView\">CodeEditTextView</a></p>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://github.com/CodeEditApp/CodeEditSourceEditor\">\n        <img src=\"https://github.com/CodeEditApp/CodeEditTextView/blob/main/.github/CodeEditSourceEditor-Icon-128@2x.png?raw=true\" height=\"128\">\n      </a>\n      <p><a href=\"https://github.com/CodeEditApp/CodeEditSourceEditor\">CodeEditSourceEditor</a></p>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://github.com/CodeEditApp/CodeEditLanguages\">\n        <img src=\"https://github.com/CodeEditApp/CodeEditLanguages/blob/main/.github/CodeEditLanguages-Icon-128@2x.png?raw=true\" height=\"128\">\n      </a>\n      <p><a href=\"https://github.com/CodeEditApp/CodeEditLanguages\">CodeEditLanguages</a></p>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://github.com/CodeEditApp/CodeEditCLI\">\n        <img src=\"https://github.com/CodeEditApp/CodeEditCLI/blob/main/.github/CodeEditCLI-Icon-128@2x.png?raw=true\" height=\"128\">\n      </a>\n      <p>&nbsp;&nbsp;&nbsp;&nbsp;<a href=\"https://github.com/CodeEditApp/CodeEditCLI\">CodeEditCLI</a>&nbsp;&nbsp;&nbsp;&nbsp;</p>\n    </td>\n  </tr>\n</table>\n"
  }
]