aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--Cargo.lock229
-rw-r--r--Cargo.toml10
-rw-r--r--flake.lock87
-rw-r--r--flake.nix101
-rw-r--r--src/main.rs183
6 files changed, 613 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d3e728e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
1.envrc
2.direnv
3/target
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..052dd11
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,229 @@
1# This file is automatically @generated by Cargo.
2# It is not intended for manual editing.
3version = 3
4
5[[package]]
6name = "autocfg"
7version = "1.0.1"
8source = "registry+https://github.com/rust-lang/crates.io-index"
9checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
10
11[[package]]
12name = "bitflags"
13version = "1.3.2"
14source = "registry+https://github.com/rust-lang/crates.io-index"
15checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
16
17[[package]]
18name = "cc"
19version = "1.0.72"
20source = "registry+https://github.com/rust-lang/crates.io-index"
21checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
22dependencies = [
23 "jobserver",
24]
25
26[[package]]
27name = "cfg-if"
28version = "1.0.0"
29source = "registry+https://github.com/rust-lang/crates.io-index"
30checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
31
32[[package]]
33name = "form_urlencoded"
34version = "1.0.1"
35source = "registry+https://github.com/rust-lang/crates.io-index"
36checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
37dependencies = [
38 "matches",
39 "percent-encoding",
40]
41
42[[package]]
43name = "git2"
44version = "0.13.25"
45source = "registry+https://github.com/rust-lang/crates.io-index"
46checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6"
47dependencies = [
48 "bitflags",
49 "libc",
50 "libgit2-sys",
51 "log",
52 "openssl-probe",
53 "openssl-sys",
54 "url",
55]
56
57[[package]]
58name = "idna"
59version = "0.2.3"
60source = "registry+https://github.com/rust-lang/crates.io-index"
61checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
62dependencies = [
63 "matches",
64 "unicode-bidi",
65 "unicode-normalization",
66]
67
68[[package]]
69name = "jobserver"
70version = "0.1.24"
71source = "registry+https://github.com/rust-lang/crates.io-index"
72checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa"
73dependencies = [
74 "libc",
75]
76
77[[package]]
78name = "libc"
79version = "0.2.116"
80source = "registry+https://github.com/rust-lang/crates.io-index"
81checksum = "565dbd88872dbe4cc8a46e527f26483c1d1f7afa6b884a3bd6cd893d4f98da74"
82
83[[package]]
84name = "libgit2-sys"
85version = "0.12.26+1.3.0"
86source = "registry+https://github.com/rust-lang/crates.io-index"
87checksum = "19e1c899248e606fbfe68dcb31d8b0176ebab833b103824af31bddf4b7457494"
88dependencies = [
89 "cc",
90 "libc",
91 "libssh2-sys",
92 "libz-sys",
93 "openssl-sys",
94 "pkg-config",
95]
96
97[[package]]
98name = "libssh2-sys"
99version = "0.2.23"
100source = "registry+https://github.com/rust-lang/crates.io-index"
101checksum = "b094a36eb4b8b8c8a7b4b8ae43b2944502be3e59cd87687595cf6b0a71b3f4ca"
102dependencies = [
103 "cc",
104 "libc",
105 "libz-sys",
106 "openssl-sys",
107 "pkg-config",
108 "vcpkg",
109]
110
111[[package]]
112name = "libz-sys"
113version = "1.1.3"
114source = "registry+https://github.com/rust-lang/crates.io-index"
115checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66"
116dependencies = [
117 "cc",
118 "libc",
119 "pkg-config",
120 "vcpkg",
121]
122
123[[package]]
124name = "log"
125version = "0.4.14"
126source = "registry+https://github.com/rust-lang/crates.io-index"
127checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
128dependencies = [
129 "cfg-if",
130]
131
132[[package]]
133name = "matches"
134version = "0.1.9"
135source = "registry+https://github.com/rust-lang/crates.io-index"
136checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
137
138[[package]]
139name = "openssl-probe"
140version = "0.1.5"
141source = "registry+https://github.com/rust-lang/crates.io-index"
142checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
143
144[[package]]
145name = "openssl-sys"
146version = "0.9.72"
147source = "registry+https://github.com/rust-lang/crates.io-index"
148checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb"
149dependencies = [
150 "autocfg",
151 "cc",
152 "libc",
153 "pkg-config",
154 "vcpkg",
155]
156
157[[package]]
158name = "percent-encoding"
159version = "2.1.0"
160source = "registry+https://github.com/rust-lang/crates.io-index"
161checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
162
163[[package]]
164name = "pkg-config"
165version = "0.3.24"
166source = "registry+https://github.com/rust-lang/crates.io-index"
167checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
168
169[[package]]
170name = "prompt"
171version = "0.1.0"
172dependencies = [
173 "git2",
174 "tico",
175]
176
177[[package]]
178name = "tico"
179version = "1.0.0"
180source = "registry+https://github.com/rust-lang/crates.io-index"
181checksum = "b9d73fbfe0205d9c15fb2768ac13d5bba2c39909bf326f5b36818aade6739464"
182
183[[package]]
184name = "tinyvec"
185version = "1.5.1"
186source = "registry+https://github.com/rust-lang/crates.io-index"
187checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
188dependencies = [
189 "tinyvec_macros",
190]
191
192[[package]]
193name = "tinyvec_macros"
194version = "0.1.0"
195source = "registry+https://github.com/rust-lang/crates.io-index"
196checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
197
198[[package]]
199name = "unicode-bidi"
200version = "0.3.7"
201source = "registry+https://github.com/rust-lang/crates.io-index"
202checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
203
204[[package]]
205name = "unicode-normalization"
206version = "0.1.19"
207source = "registry+https://github.com/rust-lang/crates.io-index"
208checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
209dependencies = [
210 "tinyvec",
211]
212
213[[package]]
214name = "url"
215version = "2.2.2"
216source = "registry+https://github.com/rust-lang/crates.io-index"
217checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
218dependencies = [
219 "form_urlencoded",
220 "idna",
221 "matches",
222 "percent-encoding",
223]
224
225[[package]]
226name = "vcpkg"
227version = "0.2.15"
228source = "registry+https://github.com/rust-lang/crates.io-index"
229checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..9c24663
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,10 @@
1[package]
2name = "prompt"
3version = "0.1.0"
4edition = "2018"
5
6# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7
8[dependencies]
9tico = "1.0.0"
10git2 = "0.13.25"
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..1e575e3
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,87 @@
1{
2 "nodes": {
3 "fenix": {
4 "inputs": {
5 "nixpkgs": [
6 "nixpkgs"
7 ],
8 "rust-analyzer-src": "rust-analyzer-src"
9 },
10 "locked": {
11 "lastModified": 1642141613,
12 "narHash": "sha256-WFS7T5oOLD8anu/77iz7EPz7zQsJZtP5qVa5485k3dI=",
13 "owner": "nix-community",
14 "repo": "fenix",
15 "rev": "d75ffc9f1d3b60bb597e48ae01f486a6de27d30e",
16 "type": "github"
17 },
18 "original": {
19 "owner": "nix-community",
20 "repo": "fenix",
21 "type": "github"
22 }
23 },
24 "gitignore": {
25 "inputs": {
26 "nixpkgs": [
27 "nixpkgs"
28 ]
29 },
30 "locked": {
31 "lastModified": 1635165013,
32 "narHash": "sha256-o/BdVjNwcB6jOmzZjOH703BesSkkS5O7ej3xhyO8hAY=",
33 "owner": "hercules-ci",
34 "repo": "gitignore.nix",
35 "rev": "5b9e0ff9d3b551234b4f3eb3983744fa354b17f1",
36 "type": "github"
37 },
38 "original": {
39 "owner": "hercules-ci",
40 "repo": "gitignore.nix",
41 "type": "github"
42 }
43 },
44 "nixpkgs": {
45 "locked": {
46 "lastModified": 1642069818,
47 "narHash": "sha256-666w6j8wl/bojfgpp0k58/UJ5rbrdYFbI2RFT2BXbSQ=",
48 "owner": "nixos",
49 "repo": "nixpkgs",
50 "rev": "46821ea01c8f54d2a20f5a503809abfc605269d7",
51 "type": "github"
52 },
53 "original": {
54 "owner": "nixos",
55 "ref": "nixpkgs-unstable",
56 "repo": "nixpkgs",
57 "type": "github"
58 }
59 },
60 "root": {
61 "inputs": {
62 "fenix": "fenix",
63 "gitignore": "gitignore",
64 "nixpkgs": "nixpkgs"
65 }
66 },
67 "rust-analyzer-src": {
68 "flake": false,
69 "locked": {
70 "lastModified": 1642101527,
71 "narHash": "sha256-C4zcaWULWlrirpEt/pA85CCMBt+7SWZweMaMJ7EvrXw=",
72 "owner": "rust-analyzer",
73 "repo": "rust-analyzer",
74 "rev": "b4c31481a554d0132003228ba319bd9476fe85ae",
75 "type": "github"
76 },
77 "original": {
78 "owner": "rust-analyzer",
79 "ref": "nightly",
80 "repo": "rust-analyzer",
81 "type": "github"
82 }
83 }
84 },
85 "root": "root",
86 "version": 7
87}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..3f416a9
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,101 @@
1{
2 inputs = {
3
4 nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
5
6 fenix = {
7 url = "github:nix-community/fenix";
8 inputs.nixpkgs.follows = "nixpkgs";
9 };
10
11 gitignore = {
12 url = "github:hercules-ci/gitignore.nix";
13 inputs.nixpkgs.follows = "nixpkgs";
14 };
15
16 };
17
18 outputs =
19 { self
20 , nixpkgs
21 , fenix
22 , gitignore
23 }:
24 let
25 inherit (gitignore.lib) gitignoreSource;
26
27 supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
28 forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
29 nixpkgsFor = forAllSystems (system:
30 import nixpkgs {
31 inherit system;
32 overlays = [ self.overlay ];
33 });
34
35 chanspec = {
36 date = "2021-12-01";
37 channel = "nightly";
38 sha256 = "DhIP1w63/hMbWlgElJGBumEK/ExFWCdLaeBV5F8uWHc="; # set zeros after modifying channel or date
39 };
40 rustChannel = p: (fenix.overlay p p).fenix.toolchainOf chanspec;
41
42 in
43 {
44
45 overlay = final: prev: {
46
47 prompt = with final;
48 let
49 pname = "prompt";
50 packageMeta = (lib.importTOML ./bin/Cargo.toml).package;
51 rustPlatform = makeRustPlatform {
52 inherit (rustChannel final) cargo rustc;
53 };
54 in
55 rustPlatform.buildRustPackage {
56 inherit pname;
57 inherit (packageMeta) version;
58
59 buildInputs = [
60 final.openssl
61 final.pkgconfig
62 ];
63
64 src = gitignoreSource ./.;
65 cargoLock.lockFile = ./Cargo.lock;
66 };
67
68 };
69
70 packages = forAllSystems (system: {
71 inherit (nixpkgsFor."${system}") prompt;
72 });
73
74 defaultPackage =
75 forAllSystems (system: self.packages."${system}".prompt);
76
77 devShell = forAllSystems (system:
78 let
79 pkgs = nixpkgsFor."${system}";
80 toolchain = (rustChannel pkgs).withComponents [
81 "rustc"
82 "cargo"
83 "rust-std"
84 "rustfmt"
85 "clippy"
86 "rust-src"
87 ];
88 inherit (fenix.packages."${system}") rust-analyzer;
89 in
90 pkgs.mkShell {
91 nativeBuildInputs = [
92 pkgs.bacon
93 rust-analyzer
94 toolchain
95 ];
96 RUST_LOG = "info";
97 RUST_BACKTRACE = 1;
98 });
99
100 };
101}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..0b1ec1b
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,183 @@
1use std::{env, fmt, path::Path};
2
3use git2::{Oid, Repository, Status};
4use tico::tico;
5
6fn main() {
7 let args = env::args().collect::<Vec<_>>();
8 match args
9 .iter()
10 .map(String::as_str)
11 .collect::<Vec<&str>>()
12 .as_slice()
13 {
14 [_, "cwd", target] => print!("{}", cwd(target)),
15 [_, "vcs", target] => {
16 if let Some(status) = vcs(target) {
17 print!("{}", status)
18 }
19 }
20 _ => (),
21 }
22}
23
24fn cwd(target: &str) -> String {
25 let home = env::var("HOME").unwrap();
26
27 let home_dir_ext = format!("{}{}", home, "/");
28 if target == home.as_str() || target.starts_with(&home_dir_ext) {
29 let replaced = target.replacen(home.as_str(), "~", 1);
30 tico(&replaced)
31 } else {
32 tico(&target)
33 }
34}
35
36struct VcsStatus {
37 branch: Branch,
38 dist: Dist,
39 status: StatusSummary,
40}
41
42impl fmt::Display for VcsStatus {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 write!(
45 f,
46 "{}{}{}#[fg=colour7]",
47 self.branch, self.dist, self.status
48 )
49 }
50}
51
52enum Branch {
53 Id(Oid),
54 Ref(String),
55 Unknown,
56}
57
58impl fmt::Display for Branch {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 match self {
61 Branch::Id(id) => write!(f, "#[fg=colour3]{:.7} ", id),
62 Branch::Ref(s) => write!(f, "#[fg=colour8]{} ", s),
63 Branch::Unknown => write!(f, ""),
64 }
65 }
66}
67
68enum Dist {
69 Ahead,
70 Behind,
71 Both,
72 Neither,
73}
74
75impl fmt::Display for Dist {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 write!(
78 f,
79 "#[fg=colour8]{}",
80 match self {
81 Self::Ahead => "↑ ",
82 Self::Behind => "↓ ",
83 Self::Both => "↑↓ ",
84 Self::Neither => "",
85 }
86 )
87 }
88}
89
90enum StatusSummary {
91 WtModified,
92 IdxModified,
93 Conflict,
94 Clean,
95}
96
97impl fmt::Display for StatusSummary {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 write!(
100 f,
101 "{}",
102 match self {
103 Self::WtModified => "#[fg=colour1]×",
104 Self::IdxModified => "#[fg=colour3]±",
105 Self::Conflict => "#[fg=colour5]!",
106 Self::Clean => "#[fg=colour2]·",
107 }
108 )
109 }
110}
111
112fn vcs(target: &str) -> Option<VcsStatus> {
113 let repo = match Path::new(target)
114 .ancestors()
115 .map(Repository::open)
116 .find_map(|r| r.ok())
117 {
118 Some(r) => r,
119 None => return None,
120 };
121
122 let dist = match get_ahead_behind(&repo) {
123 Some((ahead, behind)) if ahead > 0 && behind > 0 => Dist::Both,
124 Some((ahead, _)) if ahead > 0 => Dist::Ahead,
125 Some((_, behind)) if behind > 0 => Dist::Behind,
126 _ => Dist::Neither,
127 };
128
129 let branch = match repo.head() {
130 Ok(reference) if reference.is_branch() => {
131 Branch::Ref(reference.shorthand().unwrap().to_string())
132 }
133 Ok(reference) => Branch::Id(reference.peel_to_commit().unwrap().id()),
134 _ => Branch::Unknown,
135 };
136
137 let status = repo_status(&repo);
138
139 Some(VcsStatus {
140 branch,
141 dist,
142 status,
143 })
144}
145
146fn repo_status(repo: &Repository) -> StatusSummary {
147 for file in repo.statuses(None).unwrap().iter() {
148 match file.status() {
149 // STATE: conflicted
150 Status::CONFLICTED => return StatusSummary::Conflict,
151 // STATE: unstaged (working tree modified)
152 Status::WT_NEW
153 | Status::WT_MODIFIED
154 | Status::WT_DELETED
155 | Status::WT_TYPECHANGE
156 | Status::WT_RENAMED => return StatusSummary::WtModified,
157 // STATE: staged (changes added to index)
158 Status::INDEX_NEW
159 | Status::INDEX_MODIFIED
160 | Status::INDEX_DELETED
161 | Status::INDEX_TYPECHANGE
162 | Status::INDEX_RENAMED => return StatusSummary::IdxModified,
163 // STATE: committed (changes have been saved in the repo)
164 _ => return StatusSummary::Clean,
165 }
166 }
167 StatusSummary::Clean
168}
169
170fn get_ahead_behind(r: &Repository) -> Option<(usize, usize)> {
171 let head = (r.head().ok())?;
172 if !head.is_branch() {
173 return None;
174 }
175
176 let head_name = head.shorthand()?;
177 let head_branch = r.find_branch(head_name, git2::BranchType::Local).ok()?;
178 let upstream = head_branch.upstream().ok()?;
179 let head_oid = head.target()?;
180 let upstream_oid = upstream.get().target()?;
181
182 r.graph_ahead_behind(head_oid, upstream_oid).ok()
183}