{"format":1,"manifest":{"name":"text-lab","publisher":"vyloxi","version":"1.0.0","displayName":"Text Lab","description":"Compare two texts, count and extract things, turn lists into JSON, CSV into Markdown tables, make number sequences and fake data.","engines":{"ferrum":"^0.1.0"},"categories":["Tools"],"icon":"icon.png","repository":"https://github.com/voukengdongmofrankysteve/ferrum-market","license":"MIT","main":"extension.js","activationEvents":["onCommand:textLab.markForCompare","onCommand:textLab.compareWithMarked","onCommand:textLab.countOccurrences","onCommand:textLab.countRegex","onCommand:textLab.wordFrequency","onCommand:textLab.extractEmails","onCommand:textLab.extractUrls","onCommand:textLab.extractNumbers","onCommand:textLab.listToJson","onCommand:textLab.jsonToList","onCommand:textLab.csvToMarkdown","onCommand:textLab.markdownToCsv","onCommand:textLab.numberSequence","onCommand:textLab.fakeData"],"permissions":[],"contributes":{"commands":[{"command":"textLab.markForCompare","title":"Mark Text for Comparing","category":"Text Lab"},{"command":"textLab.compareWithMarked","title":"Compare with Marked Text","category":"Text Lab"},{"command":"textLab.countOccurrences","title":"Count Occurrences of Text...","category":"Text Lab"},{"command":"textLab.countRegex","title":"Count Matches of a Regular Expression...","category":"Text Lab"},{"command":"textLab.wordFrequency","title":"Most Used Words","category":"Text Lab"},{"command":"textLab.extractEmails","title":"Extract Email Addresses","category":"Text Lab"},{"command":"textLab.extractUrls","title":"Extract Links","category":"Text Lab"},{"command":"textLab.extractNumbers","title":"Extract Numbers","category":"Text Lab"},{"command":"textLab.listToJson","title":"Lines to JSON Array","category":"Text Lab"},{"command":"textLab.jsonToList","title":"JSON Array to Lines","category":"Text Lab"},{"command":"textLab.csvToMarkdown","title":"CSV or TSV to Markdown Table","category":"Text Lab"},{"command":"textLab.markdownToCsv","title":"Markdown Table to CSV","category":"Text Lab"},{"command":"textLab.numberSequence","title":"Insert a Number Sequence...","category":"Text Lab"},{"command":"textLab.fakeData","title":"Insert Fake Data...","category":"Text Lab"}],"configuration":{"title":"Text Lab","properties":{"textLab.caseSensitive":{"type":"boolean","default":true,"description":"Whether counting is case sensitive."},"textLab.fakeSeed":{"type":"number","default":0,"description":"A number that makes fake data repeatable. 0 means different data every time."}}}}},"files":{"icon.png":{"encoding":"base64","content":"iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAc7klEQVR42u19a5Bl1XXet/be59xXvwbBPBnMgCJpNCAHuxAwkqCQA4jY2H9ysV22UBzHpQRXXK6k5FilWD0jx4/ERaVKDxz7RxwxiuTMVCoh4CgjUqAB1aRUMsZiYADBIAkJxgzT7+77OmevlR/n3tv3cZ63+/bc25lDdRU9fbvPXnvttdfrW2sRBnzKx8v6RPkEgyAAcIfcYYon5CB79rCw/BT7fJOw7IWifWJFQERdf0BC/mjIv4mkWY0AaP156vpnSXiHJKwl6fXp1peeXmr+QACBViRW3iSit5SjniNFf2M0nfYO0Eun7jzlNz9K5RNldeL+E3YQPlLWX5idhTp6BNJi/L3//fYb7ar3q2JxL4u8xymanHYVyBAAgnIVhsf4EFI2m+kS8bENMD4NbSIBPdzg4HufYRsMr+rXAfqeNvi64+qv/J9/8vSZ1kHAERCOgod2AMrHy7p10j72tcN3W18+BcZH3SlH6ZyCKRmYombtKiGtCCAi1T7UiZuTXRo7pV7CN1ZC/jfic4kHZ4PSLolro/Y7WpQJQwAI+yy2weRXrfLXfNiGRWPZY1L0JDT9yTOffOYbvTzavAMwC4Wm1N/1l7fdQB7+PSl1rzNh4Exo5Ha4vsppFVAAEu6gaiiM77kwh834YUh86Poo9k+SomCHBWJrlhuLDeNVfHhrPtjK1zXp3zn14KkXstwGKo3U4ygYBLnr2G1HUMezTsncW9yX56nrira4pyDkaCM+lPg9zG/xiHqOWse/CeI/J9TzM6L1r9ZrYt4hEe/t2vO49VHI74Z9RbwjeX0dPwxbX+dBsALxhMQXpVxlCrsLUvqJki3sKrBT0vda8Z/90Jc+dAQEwVFw+XhZb+gGaF0n9xz7yB6B/Qud1/c4JY3S3rxVrtbsN8WHaPN0fOJ1HyG1l0rqJYNx2LU+2pi6kHXblzTBetZW/66m/YoPv+afJKN+7ZlPPnM+SSVEHoA7nrrDnLrzlP8z/+m2m7WLR03e7Cnsyvv5d7marRC4h/FD1/OXGR+5PgnuclIktfm6rc3VjF/1z9uG/oX/+9tPf6fFy9QqoHy8rE/decr/2LHbblYGJ50JZ8/kgZKff5dr2BOCUL/Uhy0qzIiK+Fz/j7rvUYnaHOn534h39P2+9B8ckTR+X/SNJpK0PhrO+ij4GftCuStyZmLfpO8Wc3uMkZO3f+n2m0/decqPUgd9B2BWZtWJ+0/Yu//zrR+0gpPOpLNj8kDJ6rw27MnAjE/anFDGx0lF1JU4LMZjo4ynoayv+0cE8QCd16Z0TcmaktnBLCdv/fxHPnji/hN2dnZWxauAprV/39du2Vn16Hl32t05dW3JKqO02E7fZJOt37TX/Wb780O37GloqqibthBVrAjss1390ar2lr0LfsH9wLd/48kLmO32DrpORPlQmQCg0lDHnJKzc3J/yVeOXmf+Jkp8pus+rVSEqZO060t73SfQhgSJx2ZKvFAf81t7J1agjNKlPSXfFM1Ovdo4BlnncevRvRb/Pzhw66xTMr9e3FXwc9OOEV8239+NYvyoRPAGlloK9+U3aX3S57vG7D8BYEC5ShGRzx7/vX3fulpO/vr/fqp8vKzPnjjb9lAxK7PqKB3le44dfq8VOVO4Kq+nfqJE3BDqe89ll25rLHtkCBQlCAVpkrUfr0ntYs3CcW48/eBTr7R4HqiAI8GHfZ8/7044TmlXXthPZr7Evfz/C8uehmfZ99FPAxmgEECsUP6qvDgTjiP1xuc7eU6tq//urxy+E4Qni3uLtrgzr8WTdb0/wKnb3hJ/CQ28QULfApAh1OZqtnq+qpnw0dO/+fRT5eNl3TYC/Tp/1ik5KFzhoq33B3R7Lrt0QzTwZIAbkwDxBe60C1MykJr/WQDAiSZ3Pnbsw+/3rP/85L6SKu0pELek/7JLN54SH/a55i1QfbsqlfMVVnl84Fv//FtnFQB41n4iN+1qd4dr2XYz/7JLd4ldOhncRuq7BazAnXKtO+loW5NPAIAqH3+/Kx7/rHY0jKtV+4+POuOxccZLAuMlgfFJ60tt2WeUekkr9X3rJqicUeRqsC8/+/7jZdcsVCevh6J3m5IGCEo4IkV02aW75C4dBmV8B26CCEoXDIjo3ZPvvH29IUu3OwWTM3nDzKIuM37YjEdyEGeTGd/5rbDAFAybosn5db7dWMYtxlXQOSVd2MoBGU8dxlKXCkowviiB6FTQpa7flU018CjkgCStrzcwF8oUCUnKxOyfxFve4e/oeZSrRDkaqPi3GGY+pIwCaSKxcQeLYjeHQGBh+OKvmwLSsXjq+T1ZvwVJusG90vM5Wof9dSOIeqxcNCEKIoCCgoJubpkMxHgCQdE6XdRz4NNJPQ3kfUR5PYoUNDREJOQwUNJFCDBAiog0gRmHDIH2dd+B2aReQcETD3WuIa/ymDRTzCIpJDRZJdCALh2BULVVtcorcMhFjnLgJlYtrcQrUvDYQ83WUKACJs0UJ0pfmNDEqQrK5g4TCFW/qlbsClxykVPrdCEGe9t/sIKcPgn2GVJqn3IVoIjE9t5l8de9EoUKr2GHcwX/o92/vHbzzK31Xfk9PjMTpUDwZ1DhGR6BIS3zjQV9Zul59/Hzj5Zer5wzRVVcD9smSJ2CQsUP6PrF/b+0dssVt9Z3F/f4LEwZltF/ccpGqAK0UrJQX9DPLzzvPvrGY6VzK6+boi6ihRRNlY8RgIhIOQpKYR/d+We38sT+Ik1cXcR6ACg5iKOgUJMaPjB5U/1fXve7i9POjB8CfbyUT8tbUn927uGpx//usZILN1SH9t5oNVvD35+5qf6pg/96ccYdXboefvk/Tv3PHz1ecsjpPwBRNhwD5ChU3lpF5c01MehL+VCiZa+gUJE1/OTkTzeOvveP5wDAh69UMsh4Kx9iZlJK4ZPXP7hY1CX5L288MjFppmDZhupZheBGu2n6pxt/8JN/FNDFvlJqNOl68H3/bLFoivLlc8cmpswUrNhoxofB8whkUuXlu6xVQgMepvUV/K+u+/QCALKwMDAYtUcpBQbDwqqPX/uJ5ReXXnCfX/quW1BFcCdkvqlfPfGww7mCf+fQ767TpUabrn/87geWX5h/0f3bhe+6RVWE7S0FCLsYOswGlTU9i+YVee9VP1eZdmZ8Hz40NEb1UVDt6/GXrvmVFer4vpM2IoUq13Dvnp+rzDgzvs9jQBcHi/+V6395haDAfaHWBFCuNCFhkiFLx2KRV3m55YoPVQNzkjDqT1M10aHpQ96e/F7b4AaoJxJn2SJPeTl81eGALjUGdAWqiW6YOeTtLXTQlQGip5KTIesnh0BgWEzqKdnlXMXjdAAYDEc5vNPdaa1YULO6VZoxcisWU2ZKdubGkC7t8K58k64O/ysyUdY0AIRUh+KWbh8tzh8VEViM59MKn0iofpRNcEMvHV3xTO/06pql3SIwoWCOmCAJI2WQZnR3KhoChjF+JAmbSd1h7eY3JmtkjrLuFDO2UClu6ExkeRi8tRbMIDkwQW+Qt49Y05WXl/7IVVggIdWKmAOGbKUPbS2gdWbmU1cpcsIrwNDNTMOWkdV8Z/bTTLGCLYKmCsiS/qTUzBcAZJeWFJQapOVHyrAIAcygfF5ULsdgpjSHrh+Nk07qNQK6FhorSpEaqt4QMPI6J3ntMoMp8dC1hTOe8Z2JKDNQlwyJoTuQQmm8+qq7+NBDM7y0pKD18A5Au3mCkskHHlgp3nVXBdZS1E0gSaAMiZVCObv0Q/fIS4/MLDaWlSY1XLtBAroevO6+lfv2Hq5YMEXeBLIOU5coBkl/LMDEQqQwAP4u2HhafOihGXvunMHERHAohsp9gvg+lr7whRnnfe/znP37/bYKCt2s1nUvfSVVkWRBAWA6+tIjM99b/ZGZ0IWOTNyQyALBF4s/fPlrMzdMX+cdKO32GRypfmIR1RHvMJD0iFtKqfft4qLipSWFUim4os2Qw6kiQD4PVKvgCxcU9u+XdpelhJYIkvLqV1BYaKypBW9FTag8FAiK9NBNe5dcrHId52tz6kBpt8RiY9IyvtcIDIsDZO6N02uNa71uCMoWOFhtpWbS20qSzQ8MICZByFX1onOG9LTCu07SYZOUYBPpvQE22hRpww30hnQQYn4ciUbOEHDZ6odTuoGRYd8IMk1mxlOG7om0xeGiFh4sbX80SSpu6bfKu/CBW0UWKFXcIQvjuw5AVyyA0kAREhaczwuIAN8HHGf4waAW430/eHdGkZKUhz+vctIyzBxlwMJDPgqBRe+LxYTOS2Y0UlKQSACzkVq6UN1vLVQux1MPPLC89MUvTqNSoUySOegBsBaFn//5invwYAPMkW5gp60jnYhbidf9Foy8cfnB639h+Q9f/ur0mq0Sdfrbm8764PGF8YtX31G5cfq6Bse5gRmkvt8N3MxWp4HxR8W77644Bw96fOGCyhqdy64gGVQoiHvwYCPoT6vSBYEycC8w/pju23tb5YbpA9752pxyyHTn4LGZAWDACqOkC3LjzIEGgPhAUGSBbhThQWDfbLh6NvomIGf/fg/792+hpcSpmJ+2g2nETUAHSru9A6XdW2gARkcBJU1OQ8LaijVVQJb2KJn0XfMm2FJvIMtNQz01BZLtJhAWYItAI2nyAKkZ3xMBjcQDpC1ZGkZ2bkvTwoPm59SIp7wjgL5dNkI7GZShaoVk/PPm4eHSdNnAsaAvjPFpkkFJTRrWc8vbYKO64gDbhPESz3gJxwNIs64vHA+AsH72gm2yYZQEEhrDUx3ucov0NrQL/jMLjQW41kEJJdgW0i9tEWUKrIXneWqLELKiteZEcZbOPnsyCLxtNOmK6FYWpsoVKfjWx3xtEcaKj7nGHKgBTGEKGrqjuIAGFQtZXV1zX3vtBzO+76nhX7ECIiX79+9d2bnzykr8ZtFGbMDRpStFPqcVwl6qr+BC9SLqtg5DICgQVrwVVLmKGXcH8qrQPD0S3h4leefo3LkfzFQqVaO1GronGAQaLb7//TdmJiZKXrFY8AdymcacrqhYTXN6Exq2gfn6IipSgwSHax0PEJRD+3indhFFXcS0Mw1HOQHoQfrK8GMfz/OU563X1G1FToiIICKo131VLGa42TMkg0aZrqj0tiIFKxZLjUUsN1bAzNBGt2H9HckgAklwq635a6jZGibNJCbMJBQpiPBY4KdTMyW24dUY09WOVQQjaFa8NSzWl+Cx14Syqq49MGENjgOGCxYai1jzK5h0JlHSpS48+bh7ANKLc90m3g0JNfsbVLHUWEHVr4F6wOWdHVhMHIyo1SVjrj6HiqpgypmCUc5Y+81psI9j9TSRwC3GN9jDYmMJq401CKQPPyjoDuaZNi+5XTLWVzZNIFS5hlq9BkMOTN7EgiJUAAmHhAwMHTZzW+8eJBScZNSNMl0ee3inNgcw4IsPBdXNI+n1DIKbX7Ute4ovEKWmjVD1q5hrzAXNCBCVk9F8zTX7lolImCVoaDTkL2bBrl1XVqamSo1UsYABspyjSVew8Pn6IpbrK6G1RP2I587SMMkGLlSk0kCiaOfOKyuTkyWvXm8oomG7TAxjjExMpGB+iK+cQZZHly4JZ3xcsEjaKiCk6jO+OjjdZhUKBa9QKGx1LDSdHcADZzlHlq5o/lBkyb8Bh+QPNjBNe6PDqbcMNNyHB8iU5KKxMHBDGN/btNMkNxNAqpbm45w9a9XLj7NDIJLM+NC6AKF4nLxsx5x5TxHldooDQNazgWn6C5uhS/yo9QdIU0Q5rqlgoVQ1HhKJCu5zB2lwYOgI9weIHbKwXcBOMQZ8Z09g05nhk5get5mqvUa9P4CkMaDGCw8Qp+cRMxzDZJlZQ+mlcGT7A/SCIjOq/9HGA6SpDu4zAnu7ZFD2CtNx6g9ApKBJgaDaqO6gwidV25eRxwNkGXDRRAWHDysaKG8+wv0BWmte9VYxX1tEyfHaWAcCwWcf7MbX+Yw8ziFFow8JUwFZGJ+qpGoU+wNw0Bd1pbGKhcYCPPbAvF7t64kPBvejoMYFD5CR8d3VwSlryimLrzyi/QGCiRvBFzpUgJKt7fy1VQUv0QIb3PwmVU25DMiEEe0P0AcD3zYwd6ScPkJtVWHCmubJBqd0jnp/gEwjZscN55CC8eEdQjajS9gY9AcQGTzL2cIDvP76G9PMsiVnQASpcA5CzYRuVNlbBNkm7STK1DDbMegPEI0PpLHGA6RifFirWGRpFSspvYAR7Q8QJvHULqnaBniAOMaH0J+xVSwh9fykUe0P0OUr06C4cBpNRHBTzycEbaQLFJqq0xQNVlav1IhWT0cXUY5zGliSmkOjHwLX3Sq2b3QspWutgjFMmw46hn7UvcC0/R6kIxsoETokMhYw1tCZzWmKOWrzArI2+ojEA0QNN95e4zUiaumA8Z8XkCKR12nzr7eISYMmkfHPmwNNRLBk925Gfl5AyrA+damADqs+NSJYxrk/QDRtkiyFoz0vIG1YX9K2ipWQYSEpfeWRz5unaaw4ZvMCBpnSbgRpEcHpFz7SeXOJOdiyDeYFpOFdRx2EyZpXvpSZ3s3Im8sGMoEjPS8A6aReekbImb7WL5QMI9oegZN+fGAa2kZ5XkAiIjjk8jBprowBiijHK28u6a3ykZ0XENkAM4TxnZAwSXVlZGyqPC55c+kN7NP2mBeQgvHr1cFZpCKsG+WY5c3Dr3uKnR04bvMC+uZlSTQgxqRmfPtEpiJ6ZPPmcUWU4z4vQNpSL7GTQ8KrgxFnBFJ85clY1tF3F1GmQTyP4ryAbiORMgWBAhsgbIpWytmz45k37y+izDIQe1TnBYQZghIbB4gdGEHJgYTtXEQ57vMCEt3BzmRQb6aIKBWaRMb0FHBc8F/Gl67WutM0+uiuDkZ3l4w07iCBoIceBh3Wdaoj8XFEWxHeHc5joIOW/0gZ1pe2sUnx2LieE6WgUPEqNF+bH6u4kFZB0erF6rzSpLuin9ysGFprVGiuOp50vVOZV13h4p7cfxR/VRLju2xDERgyWGws0+m3v5MDwLyVHUAGfCxbAJBX5l8z55a/r3PKBUunAycwMFioL9Pp8389PnQFLZ/lpblz5tWFH+icdoN6xwTGS9cMUElZYSrrmTFXuzj2va+WgqCf2kJ41IAbFTS1lD8/88jEWqNKinTf5ogwctrFX7z4tfGhKyi7l4effWRizauShu6X5QQInOqNjklC6JBZUFAFnF18xXzur/94SkGxsMBnfyQlv2EbcLVrH3v9ieLx1x4rTLoT611OpVOaBAVdwAtzr5jPnv53Y0PXo68+UfyvLz9emHQn4Hd2bw2TeumF9wN6xx1XzqqSIT1hAmVIyaACgcBVLp6b+65r2afDe26tKwrablm2W9JCNelLkYIiJVppeez1J0qfPv1vp63YIJQaIRUigpx28eyFM65lSx/ed8tI0/Xoq0+UPvXkH0z7wqF09Ut8wHgiAld8cMUXuvboQXGvcuHuykF8SZdX7viDy/4KPrL7cP03b/iN1ZuvvMlzjTsq9ya9PP+a+fMXHimdePWxoiEDtzUAI2msigBL3ipu33dr/bdv+qerH9w9WnS9dPGc+dPnHin95UuPh9LV77lRV8iQDMG7WIOdr4OunX3fj50r3H3u3pyAe4JiiVVDgTu44q1CQ+Gayavt/ol9vmXpz/9Ewa9SZSApxTycdUNVKS0XK/P6taUfmDV/DVPOZJePH1c92/qRJo3lJl3XTl5tr5na57MwdU+d7k+VJ0VOWeIgtPH5GENKLlTm9asLPzSr3hqm3cnQ0T6hjO/cfwXx366SXai/acB4E4J96OwgloLxrX+zYjFhShAIfrT6lj63/ENNYS9O23amq5drZ9VL+qbWAoEhjZzKYcaZbuv8NIzv1LOTpgR00JVqHYn7RwOn3kUEWmnkdQ47ctMt7yY60RWtyoNpgaA3DRReZCsfZF+EFCXgA8MXb5u9dlxykTO5gcCJcT1uM/cwbn6YRWDFZmJ85zeta9VVLlyVG5jx6wM3B8dddApIi64BGB9srRWBFYDwogGpb3ODf008IcpRJsaHSZ70uBKDMV42wPi0TKFk7GNXeFgyg2alq9BGNigUSfRRuvURAF9IPIaAvm1E0dNS5TrXOKfzGsLSz/iBTvwAen5om5OxdBpZGmcnMR6jwfiOfeaaVbZm62LwtCpZOQfh17hqISIsiC+djmyvkjL0mITGiS1yTBmrkJjh0NnXF5NilSg0DiU3bdowbf0h/Fja2gXAwlKzAMtr8zNyTp09erYBor9iT8AN5qDKgi4zPmq0XAzjJYHxSfuXCFyNYDzS0Nbaco9ZfAYp/BWOnm0oAJCc+rJd9a1dtpqILjN+ixmfbn3RjJc0iTwBQARe8TVXfMt5fDnwCMvQb3zm7Fnx5RlbtSQ+21bvhMuMHwXGY2DGS0gMWeqW2PIz8w+dPYsytEK5HLzC0Z/jqoW/7AdQpwH14PZnPLaY8bRxxgf5bthVD7bmgzR9LvhBuUnJLBSOgq/5N+89qSecu929BUuGdIT3s4W+/Oa7dNvTso+vDAYBbMX6b1e0rHnfmHv47D0tnnch25Tr/hZXrefP1wlErUIYpCkeSRpWlAajNphLR+k2Z0MSGW3Zy6W07NP0AwhgfsLzdZKq78HRv4Wm1AcIKQA4BSkfL+vTDz71zo6P7FQQuRMEq4pGCfdM2Er05WmLfHnakC+fbn00cCwku8TTBuMoEe6mIvBKw/Kar8nK78994YX/hnJZ4+GHuf9OK0PjOPia3zt4UhX1Xc6evK9cbZphw20RxEnPeGxBEIwyry8TbYogDev7F6pGqvzE3MNn7kEZCifa0+N7wM3vD2jXBf1xrtoL/oW6EZ8tFK0DRi5b9pfepUsKMnFriAZbO1c3XLcXtJGPAwBOdP/l7gNwFIxZ0Pc/88LbbOk+u+YvNN6qaW4egt7Bw5cZf4lcurj1BblswLK1F2raVuwCoO+78PkX3sbsLPVWmocjYI9D437Yq3/v0M0KfFKVzA6zM+dr1xixso2u+q3Md9CQPYf27CRww/r+xZrhql0gq+6Z+9O//Q7KZY0TJ2xYDWL/cz8sZu8wP/79F7/DUPfYij3vvVU33mLDJ03SNWxygNBmvESlkPgUyZpkr4VidfBGvRZJMPAk6wSzuP1rqRWlxF9p+N7bVWOr/vk282fvMGHMX/cCwp5TP2QcL+vlf/HNH8986F1fFYsbpCHv4RqTymurjFJdDaZTIn6Gadmn9+Vpi3z5IVj26GE8EaAVxBdr52qKV30lnj2pjfsP57743CuBxf+/7ODFm2XoltW47zMHj0DwaV0wrp522EwZIaWUsJBwyFDmbWLZb3kQJ6m6l5qMJxKxzLzska34Sqp+Q4A/WvjSmSO9vENkpVTScxaCWSh8E1j5mYvfnP7wlf9DLA5I1b6Ha6ykwQSHfDKEJqKIQlOfEja9gtJZtRtmDG0SZiHp0NEm2UCRqXxAkYBIxGPLS562y56yqx6hIV8XTeWFL5453uSBwsPJhQ3ZyqCOlzXuD3TJ/tlDd4uVT8HKR3XJUXAIKm+g8ophSKCJIETU21Bng7i69CFj6iuE7Gt5HNL3SKK6aoasTTr7BUX8/X7QavgB6gWVtj/HEJAILER8Jqmx4rqFeAyueCxET5JSfzL/he9+I5D6cGNv8+r3Z6FwZL2W9OrP3Xij1OyvguVeCL2H8ipHRgW3AQUQ5M5AUWzt7SZIzdAjhEgesZv6xkrx3jZU3wrYY3Dd1onwPWj1dTHOVxb+w7Nn2qDDIyAczVbONHghZDNq2N7v2TvM1Xr+oNTtYWL6KbZ8EwR7iWhfswyPtoL5g9bMp1IJSMdsiio8iVilhHdjEygisLwpRG9BqedE099AmdMLUxMv4egpv834+8sqi9R3Pv8Pbow6pGVHhigAAAAASUVORK5CYII="},"extension.js":{"encoding":"utf8","content":"// Text Lab: compare two pieces of text, count and extract things, turn lists into JSON, CSV into Markdown tables,\n// make number sequences and fake data. Works on the selection, or on the whole file when nothing is selected.\n\n// ---- compare ---------------------------------------------------------------------------------------------------------\n/** A line diff by longest common subsequence. Returns [{ op: ' ' | '-' | '+', text }]. Limited to 3000 lines a side. */\nfunction diffLines(a, b) {\n  const x = a.split('\\n');\n  const y = b.split('\\n');\n  if (x.length > 3000 || y.length > 3000) throw new Error('Both texts must be 3000 lines or fewer.');\n  let start = 0;\n  while (start < x.length && start < y.length && x[start] === y[start]) start++;\n  let ex = x.length;\n  let ey = y.length;\n  while (ex > start && ey > start && x[ex - 1] === y[ey - 1]) { ex--; ey--; }\n  const mid = [];\n  const n = ex - start;\n  const m = ey - start;\n  const t = Array.from({ length: n + 1 }, () => new Uint16Array(m + 1));\n  for (let i = n - 1; i >= 0; i--) for (let j = m - 1; j >= 0; j--) t[i][j] = x[start + i] === y[start + j] ? t[i + 1][j + 1] + 1 : Math.max(t[i + 1][j], t[i][j + 1]);\n  let i = 0;\n  let j = 0;\n  while (i < n && j < m) {\n    if (x[start + i] === y[start + j]) { mid.push({ op: ' ', text: x[start + i] }); i++; j++; }\n    else if (t[i + 1][j] >= t[i][j + 1]) mid.push({ op: '-', text: x[start + i++] });\n    else mid.push({ op: '+', text: y[start + j++] });\n  }\n  while (i < n) mid.push({ op: '-', text: x[start + i++] });\n  while (j < m) mid.push({ op: '+', text: y[start + j++] });\n  return [...x.slice(0, start).map((text) => ({ op: ' ', text })), ...mid, ...x.slice(ex).map((text) => ({ op: ' ', text }))];\n}\n\nconst esc = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n\nfunction diffPage(rows, labelA, labelB) {\n  const add = rows.filter((r) => r.op === '+').length;\n  const del = rows.filter((r) => r.op === '-').length;\n  let na = 0;\n  let nb = 0;\n  const body = rows\n    .map((r) => {\n      if (r.op !== '+') na++;\n      if (r.op !== '-') nb++;\n      return `<tr class=\"${r.op === '+' ? 'add' : r.op === '-' ? 'del' : ''}\"><td class=\"n\">${r.op === '+' ? '' : na}</td><td class=\"n\">${r.op === '-' ? '' : nb}</td><td class=\"s\">${r.op === ' ' ? '&nbsp;' : r.op}</td><td class=\"t\">${esc(r.text) || '&nbsp;'}</td></tr>`;\n    })\n    .join('');\n  return `<style>\n  body { font-family: var(--ui); } h3 { margin: 0 0 6px; } .dim { color: var(--fg-dim); font-size: 12px; }\n  table { border-collapse: collapse; width: 100%; font: 12px/1.55 var(--mono, monospace); margin-top: 10px; } td { padding: 0 8px; vertical-align: top; }\n  .n { color: var(--fg-dim); text-align: right; width: 34px; user-select: none; } .s { width: 14px; } .t { white-space: pre-wrap; word-break: break-all; }\n  tr.add { background: rgba(34,197,94,.16); } tr.del { background: rgba(239,68,68,.16); }\n</style>\n<h3>${esc(labelA)} compared with ${esc(labelB)}</h3>\n<div class=\"dim\">${add} line${add === 1 ? '' : 's'} added, ${del} removed${add + del === 0 ? '. The two texts are identical.' : '.'}</div>\n<table>${body}</table>`;\n}\n\n// ---- extract and count -----------------------------------------------------------------------------------------------\nconst EMAIL = /[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\\.[A-Za-z0-9-]+)*\\.[A-Za-z]{2,}/g;\nconst URL_RE = /\\b(?:https?|ftp):\\/\\/[^\\s<>\"'`)\\]}]+[^\\s<>\"'`)\\]}.,;:!?]/g;\nconst unique = (a) => [...new Set(a)];\nconst extractEmails = (t) => unique(t.match(EMAIL) || []);\nconst extractUrls = (t) => unique(t.match(URL_RE) || []);\nconst extractNumbers = (t) => unique(t.match(/-?\\d+(?:[.,]\\d+)?/g) || []);\n\nfunction countOccurrences(text, needle, { regex = false, caseSensitive = true, wholeWord = false } = {}) {\n  if (!needle) return 0;\n  let src = regex ? needle : needle.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n  if (wholeWord) src = '\\\\b(?:' + src + ')\\\\b';\n  const re = new RegExp(src, 'g' + (caseSensitive ? '' : 'i'));\n  let n = 0;\n  let m;\n  while ((m = re.exec(text))) {\n    n++;\n    if (m[0].length === 0) re.lastIndex++;\n  }\n  return n;\n}\n\nfunction wordFrequency(text, top = 20) {\n  const counts = new Map();\n  for (const w of text.toLowerCase().match(/[\\p{L}\\p{N}_'-]+/gu) || []) counts.set(w, (counts.get(w) || 0) + 1);\n  return [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, top);\n}\n\n// ---- converting formats ----------------------------------------------------------------------------------------------\nfunction listToJson(text, { numbers = false } = {}) {\n  const items = text.split('\\n').map((l) => l.trim()).filter(Boolean);\n  const allNum = numbers && items.length && items.every((l) => l !== '' && !Number.isNaN(Number(l)));\n  return JSON.stringify(allNum ? items.map(Number) : items, null, 2);\n}\n\nfunction jsonToList(text) {\n  const v = JSON.parse(text);\n  if (!Array.isArray(v)) throw new Error('That JSON is not an array.');\n  return v.map((x) => (typeof x === 'string' ? x : JSON.stringify(x))).join('\\n');\n}\n\n/** Parses CSV or TSV with quotes (\"a, b\", \"say \"\"hi\"\"\"). */\nfunction parseCsv(text, delimiter) {\n  const rows = [];\n  let row = [];\n  let cell = '';\n  let q = false;\n  const d = delimiter || (text.split('\\n', 1)[0].includes('\\t') ? '\\t' : text.split('\\n', 1)[0].split(';').length > text.split('\\n', 1)[0].split(',').length ? ';' : ',');\n  for (let i = 0; i < text.length; i++) {\n    const c = text[i];\n    if (q) {\n      if (c === '\"') { if (text[i + 1] === '\"') { cell += '\"'; i++; } else q = false; }\n      else cell += c;\n    } else if (c === '\"' && cell === '') q = true;\n    else if (c === d) { row.push(cell); cell = ''; }\n    else if (c === '\\n' || c === '\\r') {\n      if (c === '\\r' && text[i + 1] === '\\n') i++;\n      row.push(cell);\n      cell = '';\n      if (row.length > 1 || row[0] !== '') rows.push(row);\n      row = [];\n    } else cell += c;\n  }\n  row.push(cell);\n  if (row.length > 1 || row[0] !== '') rows.push(row);\n  return rows;\n}\n\nfunction csvToMarkdown(text, delimiter) {\n  const rows = parseCsv(text, delimiter);\n  if (!rows.length) throw new Error('There is no table in that text.');\n  const width = Math.max(...rows.map((r) => r.length));\n  const cell = (s) => (s ?? '').replace(/\\|/g, '\\\\|').replace(/\\n/g, ' ').trim();\n  const grid = rows.map((r) => Array.from({ length: width }, (_, i) => cell(r[i])));\n  const w = Array.from({ length: width }, (_, i) => Math.max(3, ...grid.map((r) => r[i].length)));\n  const line = (r) => '| ' + r.map((c, i) => c.padEnd(w[i])).join(' | ') + ' |';\n  return [line(grid[0]), '| ' + w.map((n) => '-'.repeat(n)).join(' | ') + ' |', ...grid.slice(1).map(line)].join('\\n');\n}\n\nfunction markdownToCsv(text) {\n  const rows = text.split('\\n').map((l) => l.trim()).filter((l) => l.startsWith('|') && !/^\\|[\\s:|-]+\\|$/.test(l));\n  if (!rows.length) throw new Error('There is no Markdown table in that text.');\n  const q = (s) => (/[\",\\n]/.test(s) ? '\"' + s.replace(/\"/g, '\"\"') + '\"' : s);\n  return rows.map((r) => r.replace(/^\\||\\|$/g, '').split(/(?<!\\\\)\\|/).map((c) => q(c.trim().replace(/\\\\\\|/g, '|'))).join(',')).join('\\n');\n}\n\nfunction sequence({ start = 1, step = 1, count = 10, pad = 0, prefix = '', suffix = '', separator = '\\n' } = {}) {\n  if (!(count > 0 && count <= 100000)) throw new Error('Count must be from 1 to 100000.');\n  const out = [];\n  for (let i = 0; i < count; i++) {\n    const v = start + step * i;\n    const s = Number.isInteger(v) ? String(Math.abs(v)).padStart(pad, '0') : String(Number(v.toFixed(10)));\n    out.push(prefix + (v < 0 ? '-' : '') + s + suffix);\n  }\n  return out.join(separator);\n}\n\n// ---- fake data, repeatable with a seed ---------------------------------------------------------------------------------\nfunction rng(seed) {\n  let a = (seed >>> 0) || 1;\n  return () => {\n    a = (a + 0x6d2b79f5) >>> 0;\n    let t = a;\n    t = Math.imul(t ^ (t >>> 15), t | 1);\n    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n  };\n}\nconst FIRST = ['Ada', 'Amara', 'Ben', 'Chloe', 'Daniel', 'Elena', 'Farid', 'Grace', 'Hugo', 'Ines', 'Jamal', 'Kofi', 'Lena', 'Marc', 'Nadia', 'Omar', 'Priya', 'Quentin', 'Rosa', 'Samuel', 'Tara', 'Uma', 'Victor', 'Wanjiru', 'Xavier', 'Yara', 'Zoe', 'Aisha', 'Brian', 'Carla'];\nconst LAST = ['Adeyemi', 'Baker', 'Chen', 'Dupont', 'Evans', 'Fofana', 'Garcia', 'Haddad', 'Ivanov', 'Jones', 'Kamau', 'Lopez', 'Mbeki', 'Nguyen', 'Okafor', 'Patel', 'Quinn', 'Rossi', 'Silva', 'Tanaka', 'Usman', 'Vargas', 'Wagner', 'Xu', 'Yilmaz', 'Zhang'];\nconst CITIES = ['Douala', 'Paris', 'Montreal', 'Lagos', 'Nairobi', 'Accra', 'Berlin', 'Madrid', 'Lyon', 'Toronto', 'Dakar', 'Kigali', 'Lisbon', 'Oslo', 'Cairo', 'Lima', 'Hanoi', 'Seoul', 'Austin', 'Leeds'];\nconst STREETS = ['Main Street', 'Oak Avenue', 'Rue de la Paix', 'Market Road', 'Station Lane', 'Park Boulevard', 'River Street', 'Hill Road', 'Church Street', 'Maple Drive'];\nconst COMPANIES = ['Northwind', 'Contoso', 'Acme', 'Globex', 'Initech', 'Umbrella', 'Hooli', 'Stark', 'Wayne', 'Wonka', 'Aperture', 'Cyberdyne', 'Soylent', 'Tyrell', 'Vandelay'];\nconst SUFFIX = ['Ltd', 'Inc', 'Group', 'Labs', 'Studio', 'Partners', 'Works', 'Systems'];\nconst WORDS = 'alpha river stone cloud forest silver rapid quiet bright mango ocean pixel violet harbor ember lunar maple nova orbit pearl quartz raven solar tide umber vivid willow zephyr'.split(' ');\n\nconst FAKE = {\n  name: (r) => pick(r, FIRST) + ' ' + pick(r, LAST),\n  firstName: (r) => pick(r, FIRST),\n  lastName: (r) => pick(r, LAST),\n  email: (r) => (pick(r, FIRST) + '.' + pick(r, LAST)).toLowerCase() + Math.floor(r() * 90 + 10) + '@example.com',\n  phone: (r) => '+1 ' + (200 + Math.floor(r() * 799)) + '-555-' + String(Math.floor(r() * 10000)).padStart(4, '0'),\n  address: (r) => Math.floor(r() * 9000 + 100) + ' ' + pick(r, STREETS) + ', ' + pick(r, CITIES),\n  city: (r) => pick(r, CITIES),\n  company: (r) => pick(r, COMPANIES) + ' ' + pick(r, SUFFIX),\n  username: (r) => pick(r, WORDS) + '_' + pick(r, WORDS) + Math.floor(r() * 100),\n  date: (r) => { const d = new Date(Date.UTC(2020, 0, 1) + Math.floor(r() * 365 * 6) * 86400000); return d.toISOString().slice(0, 10); },\n  number: (r) => String(Math.floor(r() * 1000)),\n  price: (r) => (Math.floor(r() * 9900 + 100) / 100).toFixed(2),\n  boolean: (r) => (r() < 0.5 ? 'true' : 'false'),\n  word: (r) => pick(r, WORDS),\n  sentence: (r) => { const n = 5 + Math.floor(r() * 6); const w = Array.from({ length: n }, () => pick(r, WORDS)); return w[0][0].toUpperCase() + w[0].slice(1) + ' ' + w.slice(1).join(' ') + '.'; },\n  uuid: (r) => 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { const v = Math.floor(r() * 16); return (c === 'x' ? v : (v & 3) | 8).toString(16); }),\n};\nfunction pick(r, list) { return list[Math.floor(r() * list.length)]; }\n\n/** Rows of fake records. `fields` are keys of FAKE. `format` is lines, json, csv or table. */\nfunction fakeData(fields, count, format, seed) {\n  const r = rng(seed);\n  const rows = Array.from({ length: count }, () => Object.fromEntries(fields.map((f) => [f, FAKE[f](r)])));\n  if (format === 'json') return JSON.stringify(rows, null, 2);\n  if (format === 'csv') return [fields.join(','), ...rows.map((row) => fields.map((f) => (/[\",\\n]/.test(row[f]) ? '\"' + row[f].replace(/\"/g, '\"\"') + '\"' : row[f])).join(','))].join('\\n');\n  if (format === 'table') return csvToMarkdown([fields.join(','), ...rows.map((row) => fields.map((f) => '\"' + row[f].replace(/\"/g, '\"\"') + '\"').join(','))].join('\\n'));\n  return rows.map((row) => fields.map((f) => row[f]).join(' | ')).join('\\n');\n}\n\nexports._test = { diffLines, extractEmails, extractUrls, extractNumbers, countOccurrences, wordFrequency, listToJson, jsonToList, parseCsv, csvToMarkdown, markdownToCsv, sequence, fakeData, FAKE, rng };\n\nexports.activate = function (context) {\n  let marked = null;\n  let panel = null;\n  const reg = (id, fn) => context.subscriptions.push(ferrum.commands.register(id, fn));\n\n  async function input() {\n    const doc = await ferrum.editor.active();\n    if (!doc) { ferrum.window.showMessage('Open a file first.', 'warn'); return null; }\n    return { doc, text: doc.selectedText || doc.text, hasSel: !!doc.selectedText };\n  }\n  const wholeRange = (doc) => {\n    const lines = doc.text.split('\\n');\n    return { start: { line: 0, col: 0 }, end: { line: lines.length - 1, col: lines[lines.length - 1].length } };\n  };\n  async function replaceInput(i, text) {\n    if (i.hasSel) await ferrum.editor.insert(text);\n    else await ferrum.editor.replace(wholeRange(i.doc), text);\n  }\n  /** Puts a result at the cursor, or over the selection, or shows how many. */\n  async function output(items, title, join = '\\n') {\n    if (!items.length) return ferrum.window.showMessage('Nothing found.');\n    const pick = await ferrum.window.showQuickPick(\n      [{ label: 'Insert ' + items.length + ' result' + (items.length === 1 ? '' : 's') + ' at the cursor', what: 'insert' }, { label: 'Replace the selection with them', what: 'replace' }, { label: 'Just show them', what: 'show', detail: items.slice(0, 5).join(', ') + (items.length > 5 ? '...' : '') }],\n      { placeholder: title + ': ' + items.length },\n    );\n    if (!pick) return;\n    if (pick.what === 'show') {\n      const p = await ferrum.window.showPanel({ title, html: '<style>body{font-family:var(--ui)} pre{font:13px/1.6 var(--mono,monospace);white-space:pre-wrap;word-break:break-all}</style><h3>' + esc(title) + ' (' + items.length + ')</h3><pre>' + esc(items.join(join)) + '</pre>' });\n      return p;\n    }\n    await ferrum.editor.insert(items.join(join));\n  }\n\n  reg('textLab.markForCompare', async () => {\n    const i = await input();\n    if (!i) return;\n    marked = { text: i.text, label: i.doc.name + (i.hasSel ? ' (selection)' : '') };\n    await ferrum.storage.set('mark', marked);\n    ferrum.window.showMessage('Marked ' + marked.label + '. Now select the other text and run \"Compare with Marked Text\".');\n  });\n  reg('textLab.compareWithMarked', async () => {\n    marked = marked || (await ferrum.storage.get('mark', null));\n    if (!marked) return ferrum.window.showMessage('Run \"Mark Text for Comparing\" first.', 'warn');\n    const i = await input();\n    if (!i) return;\n    try {\n      const html = diffPage(diffLines(marked.text, i.text), marked.label, i.doc.name + (i.hasSel ? ' (selection)' : ''));\n      if (panel) panel.setHtml(html);\n      else {\n        panel = await ferrum.window.showPanel({ title: 'Compare', html });\n        panel.onDidClose(() => (panel = null));\n      }\n    } catch (e) {\n      ferrum.window.showMessage(e.message, 'warn');\n    }\n  });\n  reg('textLab.countOccurrences', async () => {\n    const i = await input();\n    if (!i) return;\n    const seed = i.hasSel && !i.text.includes('\\n') ? i.text : '';\n    const needle = await ferrum.window.showInputBox({ prompt: 'Count how many times this text appears in the open file', value: seed });\n    if (!needle) return;\n    const full = i.doc.text;\n    const cs = ferrum.settings.get('textLab.caseSensitive') !== false;\n    const n = countOccurrences(full, needle, { caseSensitive: cs });\n    ferrum.window.showMessage('\"' + needle + '\" appears ' + n + ' time' + (n === 1 ? '' : 's') + (cs ? ' (case sensitive)' : '') + '.');\n  });\n  reg('textLab.countRegex', async () => {\n    const i = await input();\n    if (!i) return;\n    const src = await ferrum.window.showInputBox({ prompt: 'Count matches of this regular expression', placeholder: '\\\\bfoo\\\\w*' });\n    if (!src) return;\n    try {\n      const n = countOccurrences(i.doc.text, src, { regex: true, caseSensitive: ferrum.settings.get('textLab.caseSensitive') !== false });\n      ferrum.window.showMessage('/' + src + '/ matches ' + n + ' time' + (n === 1 ? '' : 's') + '.');\n    } catch (e) {\n      ferrum.window.showMessage('That regular expression is not valid: ' + e.message, 'warn');\n    }\n  });\n  reg('textLab.wordFrequency', async () => {\n    const i = await input();\n    if (!i) return;\n    const rows = wordFrequency(i.text, 40);\n    if (!rows.length) return ferrum.window.showMessage('No words found.');\n    const pick = await ferrum.window.showQuickPick(rows.map(([w, n]) => ({ label: w, detail: n + ' time' + (n === 1 ? '' : 's'), word: w })), { placeholder: 'Most used words. Pick one to insert it.' });\n    if (pick) await ferrum.editor.insert(pick.word);\n  });\n\n  reg('textLab.extractEmails', async () => { const i = await input(); if (i) await output(extractEmails(i.text), 'Email addresses'); });\n  reg('textLab.extractUrls', async () => { const i = await input(); if (i) await output(extractUrls(i.text), 'Links'); });\n  reg('textLab.extractNumbers', async () => { const i = await input(); if (i) await output(extractNumbers(i.text), 'Numbers'); });\n\n  reg('textLab.listToJson', async () => {\n    const i = await input();\n    if (i) await replaceInput(i, listToJson(i.text, { numbers: true }));\n  });\n  reg('textLab.jsonToList', async () => {\n    const i = await input();\n    if (!i) return;\n    try { await replaceInput(i, jsonToList(i.text)); } catch (e) { ferrum.window.showMessage('Cannot convert: ' + e.message, 'warn'); }\n  });\n  reg('textLab.csvToMarkdown', async () => {\n    const i = await input();\n    if (!i) return;\n    try { await replaceInput(i, csvToMarkdown(i.text)); } catch (e) { ferrum.window.showMessage('Cannot convert: ' + e.message, 'warn'); }\n  });\n  reg('textLab.markdownToCsv', async () => {\n    const i = await input();\n    if (!i) return;\n    try { await replaceInput(i, markdownToCsv(i.text)); } catch (e) { ferrum.window.showMessage('Cannot convert: ' + e.message, 'warn'); }\n  });\n\n  reg('textLab.numberSequence', async () => {\n    const spec = await ferrum.window.showInputBox({ prompt: 'Number sequence: start, step, count, zero padding', value: '1, 1, 10, 0', placeholder: 'start, step, count, pad' });\n    if (!spec) return;\n    const [start, step, count, pad] = spec.split(/[,\\s]+/).filter(Boolean).map(Number);\n    if ([start, step, count].some((n) => Number.isNaN(n)) || (pad !== undefined && Number.isNaN(pad))) return ferrum.window.showMessage('Write four numbers like \"1, 1, 10, 0\".', 'warn');\n    const joiner = await ferrum.window.showQuickPick([{ label: 'One per line', sep: '\\n' }, { label: 'Separated by commas', sep: ', ' }, { label: 'Separated by spaces', sep: ' ' }], { placeholder: 'How should they be laid out?' });\n    if (!joiner) return;\n    try { await ferrum.editor.insert(sequence({ start, step, count, pad: pad || 0, separator: joiner.sep })); } catch (e) { ferrum.window.showMessage(e.message, 'warn'); }\n  });\n\n  reg('textLab.fakeData', async () => {\n    const kinds = Object.keys(FAKE);\n    const chosen = [];\n    for (;;) {\n      const pickK = await ferrum.window.showQuickPick(\n        [...(chosen.length ? [{ label: 'Done: ' + chosen.join(', '), done: true }] : []), ...kinds.filter((k) => !chosen.includes(k)).map((k) => ({ label: k, detail: FAKE[k](rng(7)), key: k }))],\n        { placeholder: chosen.length ? 'Add another field, or choose Done' : 'Pick the first field of each record' },\n      );\n      if (!pickK) return;\n      if (pickK.done) break;\n      chosen.push(pickK.key);\n      if (chosen.length >= 12) break;\n    }\n    if (!chosen.length) return;\n    const count = Number(await ferrum.window.showInputBox({ prompt: 'How many records?', value: '10' }));\n    if (!(count > 0 && count <= 5000)) return ferrum.window.showMessage('Choose a number from 1 to 5000.', 'warn');\n    const fmt = await ferrum.window.showQuickPick([{ label: 'JSON array', f: 'json' }, { label: 'CSV with a header', f: 'csv' }, { label: 'Markdown table', f: 'table' }, { label: 'Lines separated by |', f: 'lines' }], { placeholder: 'Format' });\n    if (!fmt) return;\n    const seed = Number(ferrum.settings.get('textLab.fakeSeed')) || Math.floor(Math.random() * 1e9);\n    await ferrum.editor.insert(fakeData(chosen, count, fmt.f, seed));\n  });\n};\n\nexports.deactivate = function () {};\n"},"README.md":{"encoding":"utf8","content":"# Text Lab\n\nSmall text jobs that come up all the time. Commands work on your **selection**, or on the **whole file** when nothing is selected.\n\n## Compare\n\nSelect the first text and run **Mark Text for Comparing**, then select the second text (in the same file or another one) and run **Compare with Marked Text**. A panel shows a line by line diff with added and removed lines (up to 3000 lines each side).\n\n## Count and extract\n\n- **Count Occurrences of Text...** and **Count Matches of a Regular Expression...** report how many times something appears in the open file.\n- **Most Used Words** lists the 40 most frequent words.\n- **Extract Email Addresses / Links / Numbers** collect the unique matches. You can insert the list, replace the selection with it, or just look at it.\n\n## Convert\n\n- **Lines to JSON Array** and **JSON Array to Lines**.\n- **CSV or TSV to Markdown Table** (handles quotes, commas inside quotes and semicolon or tab separators) and **Markdown Table to CSV**.\n\n## Generate\n\n- **Insert a Number Sequence...**: start, step, count, zero padding; one per line, comma or space separated.\n- **Insert Fake Data...**: pick fields (name, email, phone, address, company, username, date, price, uuid, sentence...), a count and a format (JSON, CSV, Markdown table or lines). Set `textLab.fakeSeed` to get the same data every time. The data is made up and meant for tests and mock-ups.\n\nNo permissions needed.\n"}}}