Repository: j0ru/kickoff (language: rust)

Issue:
Support more applicatoin sources
Hey there,

Your launcher is one of the best looking. Really like it.

The only issue is it doesn't find 90% of my apps. 

Apps installed with .desktop files aren't discovered, whch would be almost all apps installed via FlatPak. 


Discussion / hints:
Hey,
thanks for your feedback! Tbh flatpaks weren't really on my radar, but I'm currently reworking the application discovery and execution interface and will keep your suggestion in mind!
Cool. Pretty sure if .desktop apps become searchable, flatpak installed apps will just work
Unfortunately it will take a while before I can add this feature, I'm currently pretty busy with university and when I implement it, I want to do it right
+1 for desktop file support. I cannot launch certain programs which requires flags on wayland to open (such as vscode). Desktop files support or some kind of config file where I could define flags for certain apps would be very good as this is a beautiful launcher! I'd contribute, but as I can understand you have a vision in mind on how exactly you want to do the implementation, so I'd rather just wait :)

Fix patch:
diff --git a/.gitignore b/.gitignore
index e9b5bd0..4f8b199 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,12 @@
-/target
 **/*.rs.bk
+
+# IDE folders
+.vscode/
+
+# Compilation artifacts
+/target
+
+# Profiling
 perf.data*
 flame.svg
 flamegraph.svg
diff --git a/Cargo.lock b/Cargo.lock
index 383e85e..f9ed889 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,18 +2,6 @@
 # It is not intended for manual editing.
 version = 3
 
-[[package]]
-name = "adler"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
-
-[[package]]
-name = "adler32"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
-
 [[package]]
 name = "ahash"
 version = "0.7.6"
@@ -144,12 +132,6 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
-[[package]]
-name = "bit_field"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4"
-
 [[package]]
 name = "bitflags"
 version = "1.3.2"
@@ -162,6 +144,18 @@ version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
 
+[[package]]
+name = "bstr"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
+dependencies = [
+ "lazy_static",
+ "memchr",
+ "regex-automata",
+ "serde",
+]
+
 [[package]]
 name = "bumpalo"
 version = "3.10.0"
@@ -202,6 +196,15 @@ dependencies = [
  "nix 0.22.3",
 ]
 
+[[package]]
+name = "cast"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a"
+dependencies = [
+ "rustc_version",
+]
+
 [[package]]
 name = "cc"
 version = "1.0.73"
@@ -216,26 +219,37 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
 name = "clap"
-version = "3.2.1"
+version = "2.34.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
+dependencies = [
+ "bitflags",
+ "textwrap 0.11.0",
+ "unicode-width",
+]
+
+[[package]]
+name = "clap"
+version = "3.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a836566fa5f52f7ddf909a8a2f9029b9f78ca584cd95cf7e87f8073110f4c5c9"
+checksum = "9f1fe12880bae935d142c8702d500c63a4e8634b6c3c57ad72bf978fc7b6249a"
 dependencies = [
  "atty",
  "bitflags",
  "clap_derive",
  "clap_lex",
  "indexmap",
- "lazy_static",
+ "once_cell",
  "strsim",
  "termcolor",
- "textwrap",
+ "textwrap 0.15.0",
 ]
 
 [[package]]
 name = "clap_derive"
-version = "3.2.1"
+version = "3.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "986fd75d1dfd2c34eb8c9275ae38ad87ea9478c9b79e87f1801f7d866dfb1e37"
+checksum = "ed6db9e867166a43a53f7199b5e4d1f522a1e5bd626654be263c999ce59df39a"
 dependencies = [
  "heck 0.4.0",
  "proc-macro-error",
@@ -246,9 +260,9 @@ dependencies = [
 
 [[package]]
 name = "clap_lex"
-version = "0.2.2"
+version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5538cd660450ebeb4234cfecf8f2284b844ffc4c50531e66d584ad5b91293613"
+checksum = "87eba3c8c7f42ef17f6c659fc7416d0f4758cd3e58861ee63c5fa4a4dde649e4"
 dependencies = [
  "os_str_bytes",
 ]
@@ -275,19 +289,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ed3d0b5ff30645a68f35ece8cea4556ca14ef8a1651455f789a099a0513532a6"
 
 [[package]]
-name = "crc32fast"
-version = "1.3.2"
+name = "criterion"
+version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10"
 dependencies = [
- "cfg-if",
+ "atty",
+ "cast",
+ "clap 2.34.0",
+ "criterion-plot",
+ "csv",
+ "futures",
+ "itertools",
+ "lazy_static",
+ "num-traits",
+ "oorandom",
+ "plotters",
+ "rayon",
+ "regex",
+ "serde",
+ "serde_cbor",
+ "serde_derive",
+ "serde_json",
+ "tinytemplate",
+ "tokio",
+ "walkdir",
+]
+
+[[package]]
+name = "criterion-plot"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57"
+dependencies = [
+ "cast",
+ "itertools",
 ]
 
 [[package]]
 name = "crossbeam-channel"
-version = "0.5.4"
+version = "0.5.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53"
+checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c"
 dependencies = [
  "cfg-if",
  "crossbeam-utils",
@@ -306,26 +349,26 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-epoch"
-version = "0.9.8"
+version = "0.9.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c"
+checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d"
 dependencies = [
  "autocfg",
  "cfg-if",
  "crossbeam-utils",
- "lazy_static",
  "memoffset",
+ "once_cell",
  "scopeguard",
 ]
 
 [[package]]
 name = "crossbeam-utils"
-version = "0.8.8"
+version = "0.8.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"
+checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83"
 dependencies = [
  "cfg-if",
- "lazy_static",
+ "once_cell",
 ]
 
 [[package]]
@@ -335,12 +378,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5b0dd6aab3061be00b739299a1d8b10da43848f0d857b30f30ab83226680cd06"
 
 [[package]]
-name = "deflate"
-version = "1.0.0"
+name = "csv"
+version = "1.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f"
+checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1"
 dependencies = [
- "adler32",
+ "bstr",
+ "csv-core",
+ "itoa 0.4.8",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "csv-core"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
+dependencies = [
+ "memchr",
 ]
 
 [[package]]
@@ -493,22 +549,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "exr"
-version = "1.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14cc0e06fb5f67e5d6beadf3a382fec9baca1aa751c6d5368fdeee7e5932c215"
-dependencies = [
- "bit_field",
- "deflate",
- "flume",
- "half",
- "inflate",
- "lebe",
- "smallvec",
- "threadpool",
-]
-
 [[package]]
 name = "fastrand"
 version = "1.7.0"
@@ -519,45 +559,47 @@ dependencies = [
 ]
 
 [[package]]
-name = "flate2"
-version = "1.0.24"
+name = "fontconfig"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
+checksum = "a25320ad784a9578ada0b395dd7dcd2321109404bbb341ec27e64b01a1e49b47"
 dependencies = [
- "crc32fast",
- "miniz_oxide",
+ "yeslogic-fontconfig-sys",
 ]
 
 [[package]]
-name = "flume"
-version = "0.10.13"
+name = "fontdue"
+version = "0.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ceeb589a3157cac0ab8cc585feb749bd2cea5cb55a6ee802ad72d9fd38303da"
+checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253"
 dependencies = [
- "futures-core",
- "futures-sink",
- "nanorand",
- "pin-project",
- "spin",
+ "hashbrown 0.11.2",
+ "ttf-parser",
 ]
 
 [[package]]
-name = "fontconfig"
-version = "0.5.0"
+name = "futures"
+version = "0.3.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a25320ad784a9578ada0b395dd7dcd2321109404bbb341ec27e64b01a1e49b47"
+checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e"
 dependencies = [
- "yeslogic-fontconfig-sys",
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
 ]
 
 [[package]]
-name = "fontdue"
-version = "0.7.2"
+name = "futures-channel"
+version = "0.3.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253"
+checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
 dependencies = [
- "hashbrown",
- "ttf-parser",
+ "futures-core",
+ "futures-sink",
 ]
 
 [[package]]
@@ -566,6 +608,17 @@ version = "0.3.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
 
+[[package]]
+name = "futures-executor"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
 [[package]]
 name = "futures-io"
 version = "0.3.21"
@@ -587,6 +640,17 @@ dependencies = [
  "waker-fn",
 ]
 
+[[package]]
+name = "futures-macro"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "futures-sink"
 version = "0.3.21"
@@ -605,9 +669,13 @@ version = "0.3.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
 dependencies = [
+ "futures-channel",
  "futures-core",
+ "futures-io",
+ "futures-macro",
  "futures-sink",
  "futures-task",
+ "memchr",
  "pin-project-lite",
  "pin-utils",
  "slab",
@@ -624,25 +692,13 @@ dependencies = [
 
 [[package]]
 name = "getrandom"
-version = "0.2.6"
+version = "0.2.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
+checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
 dependencies = [
  "cfg-if",
- "js-sys",
  "libc",
  "wasi",
- "wasm-bindgen",
-]
-
-[[package]]
-name = "gif"
-version = "0.11.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3a7187e78088aead22ceedeee99779455b23fc231fe13ec443f99bb71694e5b"
-dependencies = [
- "color_quant",
- "weezl",
 ]
 
 [[package]]
@@ -660,6 +716,12 @@ dependencies = [
  "ahash",
 ]
 
+[[package]]
+name = "hashbrown"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3"
+
 [[package]]
 name = "heck"
 version = "0.3.3"
@@ -705,65 +767,56 @@ dependencies = [
  "bytemuck",
  "byteorder",
  "color_quant",
- "exr",
- "gif",
- "jpeg-decoder",
  "num-iter",
  "num-rational",
  "num-traits",
- "png",
- "scoped_threadpool",
- "tiff",
 ]
 
 [[package]]
 name = "indexmap"
-version = "1.8.2"
+version = "1.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a"
+checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
 dependencies = [
  "autocfg",
- "hashbrown",
+ "hashbrown 0.12.1",
 ]
 
 [[package]]
-name = "inflate"
-version = "0.4.5"
+name = "instant"
+version = "0.1.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
 dependencies = [
- "adler32",
+ "cfg-if",
 ]
 
 [[package]]
-name = "instant"
-version = "0.1.12"
+name = "itertools"
+version = "0.10.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
 dependencies = [
- "cfg-if",
+ "either",
 ]
 
 [[package]]
 name = "itoa"
-version = "1.0.2"
+version = "0.4.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
+checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
 
 [[package]]
-name = "jpeg-decoder"
-version = "0.2.6"
+name = "itoa"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9478aa10f73e7528198d75109c8be5cd7d15fb530238040148d5f9a22d4c5b3b"
-dependencies = [
- "rayon",
-]
+checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
 
 [[package]]
 name = "js-sys"
-version = "0.3.57"
+version = "0.3.58"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397"
+checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -772,21 +825,24 @@ dependencies = [
 name = "kickoff"
 version = "0.5.1"
 dependencies = [
- "clap",
+ "clap 3.2.6",
+ "criterion",
  "css-color",
+ "csv",
  "env_logger",
  "exec",
  "fontconfig",
  "fontdue",
+ "futures",
  "fuzzy-matcher",
  "hex",
  "image",
  "log",
  "nix 0.24.1",
+ "nom",
  "notify-rust",
  "serde",
- "shellwords",
- "smithay-client-toolkit",
+ "smithay-client-toolkit 0.15.4",
  "smithay-clipboard",
  "tokio",
  "toml",
@@ -801,12 +857,6 @@ version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
-[[package]]
-name = "lebe"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7efd1d698db0759e6ef11a7cd44407407399a910c774dd804c64c032da7826ff"
-
 [[package]]
 name = "libc"
 version = "0.2.126"
@@ -879,6 +929,15 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "memmap2"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5172b50c23043ff43dd53e51392f36519d9b35a8f3a410d30ece5d1aedd58ae"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "memoffset"
 version = "0.6.5"
@@ -894,24 +953,6 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
 
-[[package]]
-name = "miniz_oxide"
-version = "0.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
-dependencies = [
- "adler",
-]
-
-[[package]]
-name = "nanorand"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
-dependencies = [
- "getrandom",
-]
-
 [[package]]
 name = "nix"
 version = "0.22.3"
@@ -996,9 +1037,9 @@ dependencies = [
 
 [[package]]
 name = "num-rational"
-version = "0.4.0"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a"
+checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
 dependencies = [
  "autocfg",
  "num-integer",
@@ -1068,6 +1109,12 @@ version = "1.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225"
 
+[[package]]
+name = "oorandom"
+version = "11.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
+
 [[package]]
 name = "ordered-stream"
 version = "0.0.1"
@@ -1115,26 +1162,6 @@ dependencies = [
  "winapi",
 ]
 
-[[package]]
-name = "pin-project"
-version = "1.0.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e"
-dependencies = [
- "pin-project-internal",
-]
-
-[[package]]
-name = "pin-project-internal"
-version = "1.0.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
 [[package]]
 name = "pin-project-lite"
 version = "0.2.9"
@@ -1154,15 +1181,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
 
 [[package]]
-name = "png"
-version = "0.17.5"
+name = "plotters"
+version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba"
+checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a"
 dependencies = [
- "bitflags",
- "crc32fast",
- "deflate",
- "miniz_oxide",
+ "num-traits",
+ "plotters-backend",
+ "plotters-svg",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "plotters-backend"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d88417318da0eaf0fdcdb51a0ee6c3bed624333bff8f946733049380be67ac1c"
+
+[[package]]
+name = "plotters-svg"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "521fa9638fa597e1dc53e9412a4f9cefb01187ee1f7413076f9e6749e2885ba9"
+dependencies = [
+ "plotters-backend",
 ]
 
 [[package]]
@@ -1220,18 +1263,18 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.39"
+version = "1.0.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
+checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.18"
+version = "1.0.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
+checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
 dependencies = [
  "proc-macro2",
 ]
@@ -1321,6 +1364,12 @@ dependencies = [
  "regex-syntax",
 ]
 
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+
 [[package]]
 name = "regex-syntax"
 version = "0.6.26"
@@ -1336,6 +1385,15 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
 [[package]]
 name = "ryu"
 version = "1.0.10"
@@ -1343,16 +1401,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
 
 [[package]]
-name = "scoped-tls"
-version = "1.0.0"
+name = "same-file"
+version = "1.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
 
 [[package]]
-name = "scoped_threadpool"
-version = "0.1.9"
+name = "scoped-tls"
+version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8"
+checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
 
 [[package]]
 name = "scopeguard"
@@ -1360,6 +1421,12 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
 
+[[package]]
+name = "semver"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a41d061efea015927ac527063765e73601444cdc344ba855bc7bd44578b25e1c"
+
 [[package]]
 name = "serde"
 version = "1.0.137"
@@ -1369,6 +1436,16 @@ dependencies = [
  "serde_derive",
 ]
 
+[[package]]
+name = "serde_cbor"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
+dependencies = [
+ "half",
+ "serde",
+]
+
 [[package]]
 name = "serde_derive"
 version = "1.0.137"
@@ -1386,7 +1463,7 @@ version = "1.0.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
 dependencies = [
- "itoa",
+ "itoa 1.0.2",
  "ryu",
  "serde",
 ]
@@ -1417,16 +1494,6 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
 
-[[package]]
-name = "shellwords"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89e515aa4699a88148ed5ef96413ceef0048ce95b43fbc955a33bde0a70fcae6"
-dependencies = [
- "lazy_static",
- "regex",
-]
-
 [[package]]
 name = "slab"
 version = "0.4.6"
@@ -1435,9 +1502,9 @@ checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32"
 
 [[package]]
 name = "smallvec"
-version = "1.8.0"
+version = "1.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
+checksum = "cc88c725d61fc6c3132893370cac4a0200e3fedf5da8331c570664b1987f5ca2"
 
 [[package]]
 name = "smithay-client-toolkit"
@@ -1450,7 +1517,7 @@ dependencies = [
  "dlib",
  "lazy_static",
  "log",
- "memmap2",
+ "memmap2 0.3.1",
  "nix 0.22.3",
  "pkg-config",
  "wayland-client",
@@ -1458,13 +1525,31 @@ dependencies = [
  "wayland-protocols",
 ]
 
+[[package]]
+name = "smithay-client-toolkit"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454"
+dependencies = [
+ "bitflags",
+ "dlib",
+ "lazy_static",
+ "log",
+ "memmap2 0.5.4",
+ "nix 0.24.1",
+ "pkg-config",
+ "wayland-client",
+ "wayland-cursor",
+ "wayland-protocols",
+]
+
 [[package]]
 name = "smithay-clipboard"
-version = "0.6.5"
+version = "0.6.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "610b551bd25378bfd2b8e7a0fcbd83d427e8f2f6a40c47ae0f70688e9949dd55"
+checksum = "0a345c870a1fae0b1b779085e81b51e614767c239e93503588e54c5b17f4b0e8"
 dependencies = [
- "smithay-client-toolkit",
+ "smithay-client-toolkit 0.16.0",
  "wayland-client",
 ]
 
@@ -1478,15 +1563,6 @@ dependencies = [
  "winapi",
 ]
 
-[[package]]
-name = "spin"
-version = "0.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c530c2b0d0bf8b69304b39fe2001993e267461948b890cd037d8ad4293fa1a0d"
-dependencies = [
- "lock_api",
-]
-
 [[package]]
 name = "static_assertions"
 version = "1.1.0"
@@ -1522,9 +1598,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "1.0.96"
+version = "1.0.98"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf"
+checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1554,6 +1630,15 @@ dependencies = [
  "winapi-util",
 ]
 
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
 [[package]]
 name = "textwrap"
 version = "0.15.0"
@@ -1590,33 +1675,23 @@ dependencies = [
 ]
 
 [[package]]
-name = "threadpool"
-version = "1.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
-dependencies = [
- "num_cpus",
-]
-
-[[package]]
-name = "tiff"
-version = "0.7.2"
+name = "time"
+version = "0.3.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7cfada0986f446a770eca461e8c6566cb879682f7d687c8348aa0c857bd52286"
+checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217"
 dependencies = [
- "flate2",
- "jpeg-decoder",
- "weezl",
+ "libc",
+ "num_threads",
 ]
 
 [[package]]
-name = "time"
-version = "0.3.9"
+name = "tinytemplate"
+version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd"
+checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
 dependencies = [
- "libc",
- "num_threads",
+ "serde",
+ "serde_json",
 ]
 
 [[package]]
@@ -1678,18 +1753,18 @@ dependencies = [
 
 [[package]]
 name = "tracing-core"
-version = "0.1.27"
+version = "0.1.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7709595b8878a4965ce5e87ebf880a7d39c9afc6837721b21a5a816a8117d921"
+checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7"
 dependencies = [
  "once_cell",
 ]
 
 [[package]]
 name = "ttf-parser"
-version = "0.15.1"
+version = "0.15.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42d4b50cba812f0f04f0707bb6a0eaa5fae4ae05d90fc2a377998d2f21e77a1c"
+checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd"
 
 [[package]]
 name = "uds_windows"
@@ -1703,9 +1778,9 @@ dependencies = [
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.0"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
+checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
 
 [[package]]
 name = "unicode-segmentation"
@@ -1713,6 +1788,12 @@ version = "1.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
 
+[[package]]
+name = "unicode-width"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
+
 [[package]]
 name = "version_check"
 version = "0.9.4"
@@ -1725,17 +1806,28 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
 
+[[package]]
+name = "walkdir"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
+dependencies = [
+ "same-file",
+ "winapi",
+ "winapi-util",
+]
+
 [[package]]
 name = "wasi"
-version = "0.10.2+wasi-snapshot-preview1"
+version = "0.11.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.80"
+version = "0.2.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad"
+checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994"
 dependencies = [
  "cfg-if",
  "wasm-bindgen-macro",
@@ -1743,9 +1835,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.80"
+version = "0.2.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4"
+checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a"
 dependencies = [
  "bumpalo",
  "lazy_static",
@@ -1758,9 +1850,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.80"
+version = "0.2.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5"
+checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -1768,9 +1860,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.80"
+version = "0.2.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b"
+checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1781,9 +1873,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.80"
+version = "0.2.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744"
+checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be"
 
 [[package]]
 name = "wayland-client"
@@ -1859,10 +1951,14 @@ dependencies = [
 ]
 
 [[package]]
-name = "weezl"
-version = "0.1.6"
+name = "web-sys"
+version = "0.3.58"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c97e489d8f836838d497091de568cf16b117486d529ec5579233521065bd5e4"
+checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
 
 [[package]]
 name = "wepoll-ffi"
@@ -1988,9 +2084,9 @@ checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
 
 [[package]]
 name = "yeslogic-fontconfig-sys"
-version = "3.0.1"
+version = "3.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb3f5a91c31bef6650d3a1b69192b4217fd88e4cfedc8101813e4dc3394ecbb8"
+checksum = "f2bbd69036d397ebbff671b1b8e4d918610c181c5a16073b96f984a38d08c386"
 dependencies = [
  "const-cstr",
  "dlib",
@@ -2000,9 +2096,9 @@ dependencies = [
 
 [[package]]
 name = "zbus"
-version = "2.3.0"
+version = "2.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e39a1fd5d53647ed7d1c9f549a3298bdc01cdbaa52792757838ec7794e67d16"
+checksum = "2d8f1a037b2c4a67d9654dc7bdfa8ff2e80555bbefdd3c1833c1d1b27c963a6b"
 dependencies = [
  "async-broadcast",
  "async-channel",
@@ -2040,9 +2136,9 @@ dependencies = [
 
 [[package]]
 name = "zbus_macros"
-version = "2.3.0"
+version = "2.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd89c7b4c6551b03718be71677ea1bee1eab73bbfb739fd81dfeae3e1e7328cb"
+checksum = "1f8fb5186d1c87ae88cf234974c240671238b4a679158ad3b94ec465237349a6"
 dependencies = [
  "proc-macro-crate",
  "proc-macro2",
@@ -2064,9 +2160,9 @@ dependencies = [
 
 [[package]]
 name = "zvariant"
-version = "3.3.0"
+version = "3.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e374c6fc8c84bd5392281285ec9ac003b0a1159d985d8e93ea17c8796d03354d"
+checksum = "cf2c71467724d4a77f0a1f0339dab10ca5d63f6a82411289cdcdfbfd47d2e407"
 dependencies = [
  "byteorder",
  "enumflags2",
@@ -2078,9 +2174,9 @@ dependencies = [
 
 [[package]]
 name = "zvariant_derive"
-version = "3.3.0"
+version = "3.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af4062552b13f1097b5e53ac7369c34efee5912fdc19c76805244f5e4ed5fa0e"
+checksum = "2c47f3630ce926a03abf21f5a8db90c60c81ed71599b5c86ad1a54fd3c7564c5"
 dependencies = [
  "proc-macro-crate",
  "proc-macro2",
diff --git a/Cargo.toml b/Cargo.toml
index a18cc63..713539b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,10 +14,9 @@ keywords = ["wayland", "launcher", "wlroots"]
 wayland-client = "0.29.0"
 smithay-client-toolkit = "0.15.2"
 fontdue = "0.7.2"
-image = "0.24.0"
+image = { version = "0.24.0", default-features = false }
 fuzzy-matcher = "0.3.7"
 nix = { version = "0.24.1", default_features = false, features = ["process"] }
-shellwords = "1.1.0"
 css-color = "0.2.4"
 exec = "0.3.1"
 xdg = "2.2.0"
@@ -31,11 +30,29 @@ x11-keysymdef = "0.2.0"
 fontconfig = "0.5.0"
 notify-rust = "4.5.6"
 clap = { version = "3.1.0", features = ["derive"] }
+csv = "1.1"
+futures = "0.3.21"
+nom = "7.1"
 
 [dependencies.tokio]
-version = "1.9.0"
-features = ["fs", "rt-multi-thread", "io-util", "time", "rt", "macros"]
+version = "1.19.2"
+features = [
+    "fs",
+    "rt-multi-thread",
+    "io-util",
+    "time",
+    "rt",
+    "macros",
+    "io-std",
+]
 default-features = false
 
 [profile.release]
 lto = true
+
+[dev-dependencies]
+criterion = { version = "0.3.5", features = ["async_tokio"] }
+
+[[bench]]
+name = "selection"
+harness = false
diff --git a/README.md b/README.md
index 5e86a62..78f7995 100644
--- a/README.md
+++ b/README.md
@@ -45,6 +45,7 @@ and if it fits the search query.
 - Remembers often used applications
 - Argument support for launched programs
 - Paste support
+- Build list from stdin
 
 ## Configuration
 
@@ -55,4 +56,3 @@ or can be found [here](https://github.com/j0ru/kickoff/blob/main/assets/default_
 
 - Include aliases in search results
 - Testing and documentation
-- Dmenu like parsing of stdin
diff --git a/benches/selection.rs b/benches/selection.rs
new file mode 100644
index 0000000..ce8c6ad
--- /dev/null
+++ b/benches/selection.rs
@@ -0,0 +1,18 @@
+use criterion::{criterion_group, criterion_main, Criterion};
+use tokio::runtime::Builder;
+
+use kickoff::selection::*;
+
+fn bench_build(c: &mut Criterion) {
+    c.bench_function("build_path", |b| {
+        b.to_async(Builder::new_multi_thread().enable_all().build().unwrap())
+            .iter(|| async {
+                let mut element_build = ElementListBuilder::new();
+                element_build.add_path();
+                element_build.build().await.unwrap();
+            })
+    });
+}
+
+criterion_group!(benches, bench_build);
+criterion_main!(benches);
diff --git a/src/history.rs b/src/history.rs
index 8823a03..b6d7c6c 100644
--- a/src/history.rs
+++ b/src/history.rs
@@ -1,72 +1,103 @@
 extern crate xdg;
 
-use std::cmp::max;
-use std::collections::HashMap;
-use std::fs::{read_to_string, write};
-use std::io;
+use log::*;
+use serde::{Deserialize, Serialize};
+use std::path::PathBuf;
 use std::time::SystemTime;
 use xdg::BaseDirectories;
 
-pub fn get_history(decrease_interval: u64) -> Option<HashMap<String, usize>> {
-    let xdg_dirs = BaseDirectories::with_prefix("kickoff").ok()?;
-    let cache_file = xdg_dirs.find_cache_file("run.cache")?;
+use crate::selection::Element;
 
-    let metadata = cache_file.metadata().unwrap();
-    let last_modified = metadata.modified().unwrap();
-
-    // calculates how many iterations of the interval has happend since last modification
-    let interval_diff = if decrease_interval > 0 {
-        SystemTime::now()
-            .duration_since(SystemTime::UNIX_EPOCH)
-            .unwrap()
-            .as_secs()
-            / (3600 * decrease_interval)
-            - last_modified
-                .duration_since(SystemTime::UNIX_EPOCH)
-                .unwrap()
-                .as_secs()
-                / (3600 * decrease_interval)
-    } else {
-        0
-    };
-
-    Some(decode_history(
-        &read_to_string(cache_file).ok()?,
-        interval_diff,
-    ))
+#[derive(Debug, Serialize, Deserialize)]
+pub struct HistoryEntry {
+    pub name: String,
+    pub value: String,
+    pub num_used: usize,
 }
 
-pub fn commit_history(history: &HashMap<String, usize>) -> io::Result<()> {
-    // We've always been at war with Eastasia
-    let xdg_dirs = BaseDirectories::with_prefix("kickoff")?;
-    let cache_file = xdg_dirs.place_cache_file("run.cache")?;
-    write(cache_file, encode_history(history))
+#[derive(Debug)]
+pub struct History {
+    entries: Vec<HistoryEntry>,
+    path: PathBuf,
 }
 
-fn decode_history(content: &str, substract: u64) -> HashMap<String, usize> {
-    let mut res = HashMap::new();
-    for line in content.lines() {
-        let words = line.splitn(2, ' ').collect::<Vec<&str>>();
-        match (words.get(1), words.get(0)) {
-            (Some(p), Some(n)) => {
-                let launches = n.parse().unwrap_or(1);
-                res.insert(p.to_string(), max(launches - substract as i64, 0) as usize)
+impl History {
+    pub fn as_vec(&self) -> &Vec<HistoryEntry> {
+        &self.entries
+    }
+
+    pub fn load(path: Option<PathBuf>, decrease_interval: u64) -> Result<Self, std::io::Error> {
+        let history_path = if let Some(path) = path {
+            path
+        } else {
+            let xdg_dirs = BaseDirectories::with_prefix("kickoff")?;
+            if let Some(path) = xdg_dirs.find_cache_file("default.csv") {
+                path
+            } else {
+                return Ok(History {
+                    entries: Vec::new(),
+                    path: xdg_dirs.place_cache_file("default.csv")?,
+                });
             }
-            _ => None,
         };
+
+        let mut res = History {
+            entries: Vec::new(),
+            path: history_path.clone(),
+        };
+
+        if history_path.exists() {
+            let last_modified = history_path.metadata()?.modified()?;
+            let interval_diff = if decrease_interval > 0 {
+                SystemTime::now()
+                    .duration_since(SystemTime::UNIX_EPOCH)
+                    .unwrap()
+                    .as_secs()
+                    / (3600 * decrease_interval)
+                    - last_modified
+                        .duration_since(SystemTime::UNIX_EPOCH)
+                        .unwrap()
+                        .as_secs()
+                        / (3600 * decrease_interval)
+            } else {
+                0
+            };
+
+            let mut rdr = csv::Reader::from_path(history_path).unwrap();
+            for result in rdr.deserialize() {
+                let mut record: HistoryEntry = result?;
+                record.num_used = record.num_used.saturating_sub(interval_diff as usize);
+                if record.num_used > 0 {
+                    res.entries.push(record);
+                }
+            }
+        } else {
+            info!("History file does not exists, will be created on saving");
+        }
+
+        Ok(res)
     }
-    res
-}
 
-fn encode_history(history: &HashMap<String, usize>) -> String {
-    let mut res = String::new();
-    for (p, n) in history.iter() {
-        if n > &0 {
-            res.push_str(&n.to_string());
-            res.push(' ');
-            res.push_str(p);
-            res.push('\n');
+    pub fn inc(&mut self, element: &Element) {
+        if let Some(entry) = self.entries.iter_mut().find(|x| x.name == element.name) {
+            entry.num_used += 1;
+            entry.value = element.value.to_owned();
+        } else {
+            self.entries.push(HistoryEntry {
+                name: element.name.to_owned(),
+                value: element.value.to_owned(),
+                num_used: 1,
+            })
         }
     }
-    res
+
+    pub fn save(&self) -> Result<(), std::io::Error> {
+        let mut wtr = csv::Writer::from_path(&self.path)?;
+        for entry in &self.entries {
+            wtr.serialize(entry)?;
+        }
+        wtr.flush()?;
+
+        Ok(())
+    }
 }
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..e6da591
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,2 @@
+pub mod history;
+pub mod selection;
diff --git a/src/main.rs b/src/main.rs
index f28ac6a..d855cfa 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,6 +1,14 @@
-use std::path::PathBuf;
-use crate::gui::{Action, DData, RenderEvent};
 use crate::config::Config;
+use crate::gui::{Action, DData, RenderEvent};
+use clap::Parser;
+use history::History;
+use image::ImageBuffer;
+use log::*;
+use nix::{
+    sys::wait::{waitpid, WaitPidFlag, WaitStatus},
+    unistd::{fork, ForkResult},
+};
+use notify_rust::Notification;
 use smithay_client_toolkit::{
     default_environment,
     environment::SimpleGlobal,
@@ -8,25 +16,8 @@ use smithay_client_toolkit::{
     reexports::{calloop, protocols::wlr::unstable::layer_shell::v1::client::zwlr_layer_shell_v1},
     WaylandSource,
 };
-
-use image::ImageBuffer;
-
-use fuzzy_matcher::skim::SkimMatcherV2;
-use fuzzy_matcher::FuzzyMatcher;
-use std::collections::HashMap;
-use std::os::unix::fs::PermissionsExt;
-use std::{cmp, env, fs, process};
-
-use log::error;
-use nix::{
-    sys::wait::{waitpid, WaitPidFlag, WaitStatus},
-    unistd::{fork, ForkResult},
-};
-use notify_rust::Notification;
-use std::error::Error;
-use std::time::Duration;
+use std::{cmp, error::Error, path::PathBuf, process, time::Duration};
 use tokio::task::JoinHandle;
-use clap::Parser;
 
 mod color;
 mod config;
@@ -34,6 +25,7 @@ mod font;
 mod gui;
 mod history;
 mod keybinds;
+mod selection;
 
 default_environment!(Env,
     fields = [
@@ -47,8 +39,27 @@ default_environment!(Env,
 #[derive(Parser, Debug)]
 #[clap(author, version, about)]
 struct Args {
-    #[clap(short, long, value_parser)]
+    #[clap(short, long)]
     config: Option<PathBuf>,
+
+    /// Read list from stdin instead of PATH
+    #[clap(long)]
+    from_stdin: bool,
+
+    /// Read list from PATH, default true, unless stdin is set
+    #[clap(long)]
+    from_path: bool,
+
+    #[clap(long)]
+    from_file: Vec<PathBuf>,
+
+    /// Output selection to stdout instead of executing it
+    #[clap(long)]
+    stdout: bool,
+
+    /// Set custom history name. Default history will only be used if stdin is not set
+    #[clap(long)]
+    history: Option<PathBuf>,
 }
 
 #[tokio::main]
@@ -75,11 +86,28 @@ async fn run() -> Result<Option<JoinHandle<()>>, Box<dyn Error>> {
         }
     };
 
-    let applications_handle = { tokio::spawn(async move { get_executable_names() }) };
-    let history_handle = {
+    let mut apps = selection::ElementListBuilder::new();
+    if args.from_path || (!args.from_stdin && args.from_file.is_empty()) {
+        apps.add_path();
+    }
+    if !args.from_file.is_empty() {
+        apps.add_files(&args.from_file);
+    }
+    if args.from_stdin {
+        apps.add_stdin();
+    }
+    let apps = apps.build();
+
+    let history = if (!args.from_stdin && args.from_file.is_empty()) || args.history.is_some() {
+        let path = args.history.clone();
         let decrease_interval = config.history.decrease_interval;
-        tokio::spawn(async move { history::get_history(decrease_interval) })
+        Some(tokio::task::spawn_blocking(move || {
+            History::load(path, decrease_interval)
+        }))
+    } else {
+        None
     };
+
     let font = if let Some(font_name) = config.font {
         let mut font_names = config.fonts.clone();
         font_names.insert(0, font_name);
@@ -92,20 +120,16 @@ async fn run() -> Result<Option<JoinHandle<()>>, Box<dyn Error>> {
         new_default_environment!(Env, fields = [layer_shell: SimpleGlobal::new(),])
             .expect("Initial roundtrip failed!");
 
-    let mut history = history_handle.await?.unwrap_or_default();
-    let mut applications = applications_handle.await?.unwrap();
-    for app in history.keys() {
-        applications.push(app.to_string());
-    }
-
-    applications.sort();
-    applications.dedup();
-    applications.sort_by(|a, b| {
-        history
-            .get(b)
-            .unwrap_or(&0)
-            .cmp(history.get(a).unwrap_or(&0))
-    });
+    let mut apps = apps.await?;
+    let history = match history {
+        Some(history) => {
+            let history = history.await??;
+            apps.merge_history(&history);
+            Some(history)
+        }
+        None => None,
+    };
+    apps.sort_score();
 
     let layer_shell = env.require_global::<zwlr_layer_shell_v1::ZwlrLayerShellV1>();
     let pools = env
@@ -121,7 +145,7 @@ async fn run() -> Result<Option<JoinHandle<()>>, Box<dyn Error>> {
 
     gui::register_inputs(&env.get_all_seats(), &event_loop);
 
-    let mut matched_exe: Vec<&String> = applications.iter().collect();
+    let mut search_results = apps.as_ref_vec();
     let mut need_redraw = false;
     let mut data = DData::new(&display, config.keybindings.clone().into());
     let mut selection = 0;
@@ -149,89 +173,55 @@ async fn run() -> Result<Option<JoinHandle<()>>, Box<dyn Error>> {
                 }
                 Action::NavDown => {
                     need_redraw = true;
-                    if select_query && !matched_exe.is_empty() {
+                    if select_query && !search_results.is_empty() {
                         select_query = false;
-                    } else if !matched_exe.is_empty() && selection < matched_exe.len() - 1 {
+                    } else if !search_results.is_empty() && selection < search_results.len() - 1 {
                         selection += 1;
                     }
                 }
                 Action::Search => {
                     need_redraw = true;
-                    matched_exe = fuzzy_sort(&applications, query, &history);
+                    search_results = apps.search(query);
                     select_query = false;
                     selection = 0;
-                    if matched_exe.is_empty() {
+                    if search_results.is_empty() {
                         select_query = true
                     }
                 }
                 Action::Complete => {
                     if !select_query {
-                        let app = matched_exe.get(selection).unwrap();
-                        if query == *app {
-                            selection = if selection < matched_exe.len() - 1 {
+                        let app = search_results.get(selection).unwrap();
+                        if query == &app.name {
+                            selection = if selection < search_results.len() - 1 {
                                 selection + 1
                             } else {
                                 selection
                             };
                         }
                         query.clear();
-                        query.push_str(matched_exe.get(selection).unwrap());
+                        query.push_str(&search_results.get(selection).unwrap().name);
                         need_redraw = true;
                     }
                 }
                 Action::Execute => {
-                    let query = if select_query {
-                        query.to_string()
+                    let element = if select_query {
+                        selection::Element {
+                            name: query.to_string(),
+                            value: query.to_string(),
+                            base_score: 0,
+                        }
                     } else {
-                        matched_exe.get(selection).unwrap().to_string()
+                        (*search_results.get(selection).unwrap()).clone()
                     };
-                    if let Ok(mut args) = shellwords::split(&query) {
-                        match unsafe { fork() } {
-                            Ok(ForkResult::Parent { child }) => {
-                                return Ok(Some(tokio::spawn(async move {
-                                    tokio::time::sleep(Duration::new(1, 0)).await;
-                                    match waitpid(child, Some(WaitPidFlag::WNOHANG)) {
-                                        Ok(WaitStatus::StillAlive)
-                                        | Ok(WaitStatus::Exited(_, 0)) => {
-                                            history.insert(
-                                                query.to_string(),
-                                                history.get(&query).unwrap_or(&0) + 1,
-                                            );
-                                            match history::commit_history(&history) {
-                                                Ok(_) => {}
-                                                Err(e) => {
-                                                    error!("{}", e.to_string())
-                                                }
-                                            };
-                                        }
-                                        Ok(_) => {
-                                            /* Every non 0 statuscode holds no information since it's
-                                            origin can be the started application or a file not found error.
-                                            In either case the error has already been logged and does not
-                                            need to be handled here. */
-                                        }
-                                        Err(err) => error!("{}", err),
-                                    }
-                                })));
-                            }
-                            Ok(ForkResult::Child) => {
-                                let err = exec::Command::new(args.remove(0)).args(&args).exec();
-
-                                // Won't be executed when exec was successful
-                                error!("{}", err);
-
-                                Notification::new()
-                                    .summary("Kickoff")
-                                    .body(&format!("{}", err))
-                                    .timeout(5000)
-                                    .show()?;
-                                process::exit(2);
-                            }
-                            Err(err) => {
-                                error!("{}", err);
-                            }
+                    if args.stdout {
+                        print!("{}", element.value);
+                        if let Some(mut history) = history {
+                            history.inc(&element);
+                            history.save()?;
                         }
-                        break;
+                        return Ok(None);
+                    } else {
+                        return Ok(Some(exec(element, history)?));
                     }
                 }
                 Action::Exit => break,
@@ -284,10 +274,10 @@ async fn run() -> Result<Option<JoinHandle<()>>, Box<dyn Error>> {
                 0
             };
 
-            for (i, matched) in matched_exe
+            for (i, matched) in search_results
                 .iter()
                 .enumerate()
-                .take(cmp::min(max_entries + offset, matched_exe.len()))
+                .take(cmp::min(max_entries + offset, search_results.len()))
                 .skip(offset)
             {
                 let color = if i == selection && !select_query {
@@ -296,7 +286,7 @@ async fn run() -> Result<Option<JoinHandle<()>>, Box<dyn Error>> {
                     &config.colors.text
                 };
                 font.render(
-                    matched,
+                    &matched.name,
                     color,
                     &mut img,
                     config.padding,
@@ -322,54 +312,49 @@ async fn run() -> Result<Option<JoinHandle<()>>, Box<dyn Error>> {
     Ok(None)
 }
 
-fn get_executable_names() -> Option<Vec<String>> {
-    let var = match env::var_os("PATH") {
-        Some(var) => var,
-        None => return None,
-    };
-
-    let mut res: Vec<String> = Vec::new();
-
-    let paths_iter = env::split_paths(&var);
-    let dirs_iter = paths_iter.filter_map(|path| fs::read_dir(path).ok());
-
-    for dir in dirs_iter {
-        let executables_iter = dir.filter_map(|file| file.ok()).filter(|file| {
-            if let Ok(metadata) = file.metadata() {
-                return !metadata.is_dir() && metadata.permissions().mode() & 0o111 != 0;
-            }
-            false
-        });
-
-        for exe in executables_iter {
-            res.push(exe.file_name().to_str().unwrap().to_string());
+fn exec(
+    elem: selection::Element,
+    history: Option<History>,
+) -> Result<tokio::task::JoinHandle<()>, Box<dyn Error>> {
+    match unsafe { fork() } {
+        Ok(ForkResult::Parent { child }) => {
+            Ok(tokio::spawn(async move {
+                tokio::time::sleep(Duration::new(1, 0)).await;
+                match waitpid(child, Some(WaitPidFlag::WNOHANG)) {
+                    Ok(WaitStatus::StillAlive) | Ok(WaitStatus::Exited(_, 0)) => {
+                        if let Some(mut history) = history {
+                            history.inc(&elem);
+                            match history.save() {
+                                Ok(()) => {}
+                                Err(e) => {
+                                    error!("{}", e);
+                                }
+                            };
+                        }
+                    }
+                    Ok(_) => {
+                        /* Every non 0 statuscode holds no information since it's
+                        origin can be the started application or a file not found error.
+                        In either case the error has already been logged and does not
+                        need to be handled here. */
+                    }
+                    Err(err) => error!("{}", err),
+                }
+            }))
+        }
+        Ok(ForkResult::Child) => {
+            let err = exec::Command::new("sh").args(&["-c", &elem.value]).exec();
+
+            // Won't be executed when exec was successful
+            error!("{}", err);
+
+            Notification::new()
+                .summary("Kickoff")
+                .body(&format!("{}", err))
+                .timeout(5000)
+                .show()?;
+            process::exit(2);
         }
+        Err(e) => Err(Box::new(e)),
     }
-
-    Some(res)
-}
-
-fn fuzzy_sort<'a>(
-    executables: &'a [String],
-    pattern: &str,
-    pre_scored: &'a HashMap<String, usize>,
-) -> Vec<&'a String> {
-    let matcher = SkimMatcherV2::default();
-    let mut executables = executables
-        .iter()
-        .map(|x| {
-            (
-                matcher
-                    .fuzzy_match(x, pattern)
-                    .map(|score| score + *pre_scored.get(x).unwrap_or(&1) as i64),
-                x,
-            )
-        })
-        .filter(|x| x.0.is_some())
-        .collect::<Vec<(Option<i64>, &String)>>();
-    executables.sort_by(|a, b| b.0.unwrap_or(0).cmp(&a.0.unwrap_or(0)));
-    executables
-        .into_iter()
-        .map(|x| x.1)
-        .collect()
 }
diff --git a/src/selection.rs b/src/selection.rs
new file mode 100644
index 0000000..246c4f1
--- /dev/null
+++ b/src/selection.rs
@@ -0,0 +1,267 @@
+use crate::history::History;
+use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
+use log::*;
+use nom::{
+    branch::alt,
+    bytes::complete::is_not,
+    character::complete::char,
+    combinator::opt,
+    sequence::pair,
+    sequence::{delimited, preceded},
+    Finish, IResult,
+};
+use std::fs::File;
+use std::{
+    cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd},
+    io::{BufRead, BufReader},
+    path::PathBuf,
+};
+use std::{env, os::unix::fs::PermissionsExt};
+use tokio::{
+    io::{self, AsyncBufReadExt},
+    task::{spawn, spawn_blocking},
+};
+
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub struct Element {
+    pub name: String,
+    pub value: String,
+    pub base_score: usize,
+}
+
+impl Ord for Element {
+    fn cmp(&self, other: &Element) -> Ordering {
+        match other.base_score.cmp(&self.base_score) {
+            Ordering::Equal => self.name.cmp(&other.name),
+            e => e,
+        }
+    }
+}
+
+impl PartialOrd for Element {
+    fn partial_cmp(&self, other: &Element) -> Option<Ordering> {
+        match other.base_score.cmp(&self.base_score) {
+            Ordering::Equal => Some(self.name.cmp(&other.name)),
+            e => Some(e),
+        }
+    }
+}
+
+#[derive(Debug, Default)]
+pub struct ElementList {
+    inner: Vec<Element>,
+}
+
+impl ElementList {
+    pub fn merge_history(&mut self, history: &History) {
+        for entry in history.as_vec().iter() {
+            if let Some(elem) = self.inner.iter_mut().find(|x| x.name == entry.name) {
+                elem.base_score = entry.num_used;
+            } else {
+                self.inner.push(Element {
+                    name: entry.name.to_owned(),
+                    value: entry.value.to_owned(),
+                    base_score: entry.num_used,
+                })
+            }
+        }
+    }
+
+    pub fn sort_score(&mut self) {
+        self.inner.sort_by(|a, b| b.base_score.cmp(&a.base_score))
+    }
+
+    pub fn search(&self, pattern: &str) -> Vec<&Element> {
+        let matcher = SkimMatcherV2::default();
+        let mut executables = self
+            .inner
+            .iter()
+            .map(|x| {
+                (
+                    matcher
+                        .fuzzy_match(&x.name, pattern)
+                        .map(|score| score + x.base_score as i64),
+                    x,
+                )
+            })
+            .filter(|x| x.0.is_some())
+            .collect::<Vec<(Option<i64>, &Element)>>();
+        executables.sort_by(|a, b| b.0.unwrap_or(0).cmp(&a.0.unwrap_or(0)));
+        executables.into_iter().map(|x| x.1).collect()
+    }
+
+    pub fn as_ref_vec(&self) -> Vec<&Element> {
+        self.inner.iter().collect()
+    }
+}
+
+#[derive(Debug, Default)]
+pub struct ElementListBuilder {
+    from_path: bool,
+    from_stdin: bool,
+    from_file: Vec<PathBuf>,
+}
+
+impl ElementListBuilder {
+    pub fn new() -> ElementListBuilder {
+        ElementListBuilder::default()
+    }
+
+    pub fn add_path(&mut self) {
+        self.from_path = true;
+    }
+    pub fn add_files(&mut self, files: &[PathBuf]) {
+        self.from_file = files.to_vec();
+    }
+    pub fn add_stdin(&mut self) {
+        self.from_stdin = true;
+    }
+
+    pub async fn build(&self) -> Result<ElementList, Box<dyn std::error::Error>> {
+        let mut fut = Vec::new();
+        if self.from_stdin {
+            fut.push(spawn(ElementListBuilder::build_stdin()))
+        }
+        if !self.from_file.is_empty() {
+            let files = self.from_file.clone();
+            fut.push(spawn_blocking(move || {
+                ElementListBuilder::build_files(&files)
+            }))
+        }
+        if self.from_path {
+            fut.push(spawn_blocking(ElementListBuilder::build_path))
+        }
+
+        let finished = futures::future::join_all(fut).await;
+
+        let mut res = Vec::new();
+        for elements in finished {
+            let mut elements = elements??;
+            res.append(&mut elements);
+        }
+
+        Ok(ElementList { inner: res })
+    }
+
+    fn build_files(files: &[PathBuf]) -> Result<Vec<Element>, std::io::Error> {
+        let mut res = Vec::new();
+        for file in files {
+            let mut reader = BufReader::new(File::open(file)?);
+            let mut buf = String::new();
+            while reader.read_line(&mut buf)? > 0 {
+                let kv_pair = match parse_line(&buf) {
+                    Ok(None) => continue,
+                    Ok(Some(res)) => res,
+                    Err(e) => {
+                        error!("Failed parsing {}", e);
+                        continue;
+                    }
+                };
+                match kv_pair {
+                    (key, Some(value)) => res.push(Element {
+                        name: key.to_string(),
+                        value: value.to_string(),
+                        base_score: 0,
+                    }),
+                    ("", None) => {} // Empty Line
+                    (key, None) => res.push(Element {
+                        name: key.to_string(),
+                        value: key.to_string(),
+                        base_score: 0,
+                    }),
+                }
+
+                buf.clear();
+            }
+        }
+
+        Ok(res)
+    }
+
+    fn build_path() -> Result<Vec<Element>, std::io::Error> {
+        let var = env::var("PATH").unwrap();
+
+        let mut res: Vec<Element> = Vec::new();
+
+        let paths_iter = env::split_paths(&var);
+        let dirs_iter = paths_iter.filter_map(|path| std::fs::read_dir(path).ok());
+
+        for dir in dirs_iter {
+            dir.filter_map(|file| file.ok()).for_each(|file| {
+                if let Ok(metadata) = file.metadata() {
+                    if !metadata.is_dir() && metadata.permissions().mode() & 0o111 != 0 {
+                        let name = file.file_name().to_str().unwrap().to_string();
+                        res.push(Element {
+                            value: name.clone(),
+                            name,
+                            base_score: 0,
+                        });
+                    }
+                }
+            });
+        }
+
+        res.sort();
+        res.dedup_by(|a, b| a.name == b.name);
+
+        Ok(res)
+    }
+
+    async fn build_stdin() -> Result<Vec<Element>, std::io::Error> {
+        let stdin = io::stdin();
+        let reader = io::BufReader::new(stdin);
+        let mut lines = reader.lines();
+        let mut res = Vec::new();
+
+        while let Some(line) = lines.next_line().await? {
+            let kv_pair = match parse_line(&line) {
+                Ok(None) => continue,
+                Ok(Some(res)) => res,
+                Err(e) => {
+                    error!("Failed parsing {}", e);
+                    continue;
+                }
+            };
+            match kv_pair {
+                (key, Some(value)) => res.push(Element {
+                    name: key.to_string(),
+                    value: value.to_string(),
+                    base_score: 0,
+                }),
+                ("", None) => {} // Empty Line
+                (key, None) => res.push(Element {
+                    name: key.to_string(),
+                    value: key.to_string(),
+                    base_score: 0,
+                }),
+            }
+        }
+
+        Ok(res)
+    }
+}
+
+#[allow(clippy::type_complexity)]
+fn parse_line<'a>(
+    input: &'a str,
+) -> Result<Option<(&str, Option<&str>)>, Box<dyn std::error::Error + 'a>> {
+    let input = input.trim_end();
+    match pair(
+        alt((is_not("\"="), quoted_string)),
+        opt(preceded(char('='), alt((is_not("\""), quoted_string)))),
+    )(input)
+    .finish()
+    {
+        Ok(("", ("", None))) => Ok(None),
+        Ok(("", res)) => Ok(Some(res)),
+        Ok((unparsed, _res)) => {
+            warn!("Input was not fully consumed: {unparsed}");
+            Ok(None)
+        }
+        Err(e) => Err(Box::new(e)),
+    }
+}
+
+fn quoted_string(input: &str) -> IResult<&str, &str> {
+    delimited(char('"'), is_not("\""), char('"'))(input)
+}


Test patch: