Extract multi_buffer module out of editor (#3170)

Piotr Osiewicz created

Release Notes:

- N/A

Change summary

Cargo.lock                                        |  51 ++++
Cargo.toml                                        |   1 
crates/assistant/Cargo.toml                       |   1 
crates/assistant/src/assistant_panel.rs           |   2 
crates/assistant/src/codegen.rs                   |   3 
crates/editor/Cargo.toml                          |   2 
crates/editor/src/display_map/block_map.rs        |   2 
crates/editor/src/display_map/fold_map.rs         |   2 
crates/editor/src/display_map/inlay_map.rs        |   6 
crates/editor/src/editor.rs                       |   5 
crates/editor/src/git.rs                          | 193 ++++++++++++++++
crates/editor/src/test/editor_lsp_test_context.rs |   4 
crates/multi_buffer/Cargo.toml                    |  80 ++++++
crates/multi_buffer/src/anchor.rs                 |  10 
crates/multi_buffer/src/multi_buffer.rs           | 198 ----------------
15 files changed, 347 insertions(+), 213 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -310,6 +310,7 @@ dependencies = [
  "language",
  "log",
  "menu",
+ "multi_buffer",
  "ordered-float 2.10.0",
  "parking_lot 0.11.2",
  "project",
@@ -2410,6 +2411,7 @@ dependencies = [
  "lazy_static",
  "log",
  "lsp",
+ "multi_buffer",
  "ordered-float 2.10.0",
  "parking_lot 0.11.2",
  "postage",
@@ -4600,6 +4602,55 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389"
 
+[[package]]
+name = "multi_buffer"
+version = "0.1.0"
+dependencies = [
+ "aho-corasick",
+ "anyhow",
+ "client",
+ "clock",
+ "collections",
+ "context_menu",
+ "convert_case 0.6.0",
+ "copilot",
+ "ctor",
+ "env_logger 0.9.3",
+ "futures 0.3.28",
+ "git",
+ "gpui",
+ "indoc",
+ "itertools 0.10.5",
+ "language",
+ "lazy_static",
+ "log",
+ "lsp",
+ "ordered-float 2.10.0",
+ "parking_lot 0.11.2",
+ "postage",
+ "project",
+ "pulldown-cmark",
+ "rand 0.8.5",
+ "rich_text",
+ "schemars",
+ "serde",
+ "serde_derive",
+ "settings",
+ "smallvec",
+ "smol",
+ "snippet",
+ "sum_tree",
+ "text",
+ "theme",
+ "tree-sitter",
+ "tree-sitter-html",
+ "tree-sitter-rust",
+ "tree-sitter-typescript",
+ "unindent",
+ "util",
+ "workspace",
+]
+
 [[package]]
 name = "multimap"
 version = "0.8.3"

Cargo.toml 🔗

@@ -46,6 +46,7 @@ members = [
     "crates/lsp",
     "crates/media",
     "crates/menu",
+    "crates/multi_buffer",
     "crates/node_runtime",
     "crates/notifications",
     "crates/outline",

crates/assistant/Cargo.toml 🔗

@@ -17,6 +17,7 @@ fs = { path = "../fs" }
 gpui = { path = "../gpui" }
 language = { path = "../language" }
 menu = { path = "../menu" }
+multi_buffer = { path = "../multi_buffer" }
 search = { path = "../search" }
 settings = { path = "../settings" }
 theme = { path = "../theme" }

crates/assistant/src/assistant_panel.rs 🔗

@@ -296,7 +296,7 @@ impl AssistantPanel {
         };
 
         let selection = editor.read(cx).selections.newest_anchor().clone();
-        if selection.start.excerpt_id() != selection.end.excerpt_id() {
+        if selection.start.excerpt_id != selection.end.excerpt_id {
             return;
         }
         let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);

crates/assistant/src/codegen.rs 🔗

@@ -1,10 +1,11 @@
 use crate::streaming_diff::{Hunk, StreamingDiff};
 use ai::completion::{CompletionProvider, OpenAIRequest};
 use anyhow::Result;
-use editor::{multi_buffer, Anchor, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
+use editor::{Anchor, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
 use futures::{channel::mpsc, SinkExt, Stream, StreamExt};
 use gpui::{Entity, ModelContext, ModelHandle, Task};
 use language::{Rope, TransactionId};
+use multi_buffer;
 use std::{cmp, future, ops::Range, sync::Arc};
 
 pub enum Event {

crates/editor/Cargo.toml 🔗

@@ -14,6 +14,7 @@ test-support = [
     "text/test-support",
     "language/test-support",
     "gpui/test-support",
+    "multi_buffer/test-support",
     "project/test-support",
     "util/test-support",
     "workspace/test-support",
@@ -34,6 +35,7 @@ git = { path = "../git" }
 gpui = { path = "../gpui" }
 language = { path = "../language" }
 lsp = { path = "../lsp" }
+multi_buffer = { path = "../multi_buffer" }
 project = { path = "../project" }
 rpc = { path = "../rpc" }
 rich_text = { path = "../rich_text" }

crates/editor/src/display_map/block_map.rs 🔗

@@ -993,8 +993,8 @@ mod tests {
     use super::*;
     use crate::display_map::inlay_map::InlayMap;
     use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap};
-    use crate::multi_buffer::MultiBuffer;
     use gpui::{elements::Empty, Element};
+    use multi_buffer::MultiBuffer;
     use rand::prelude::*;
     use settings::SettingsStore;
     use std::env;

crates/editor/src/display_map/fold_map.rs 🔗

@@ -91,7 +91,7 @@ impl<'a> FoldMapWriter<'a> {
 
             // For now, ignore any ranges that span an excerpt boundary.
             let fold = Fold(buffer.anchor_after(range.start)..buffer.anchor_before(range.end));
-            if fold.0.start.excerpt_id() != fold.0.end.excerpt_id() {
+            if fold.0.start.excerpt_id != fold.0.end.excerpt_id {
                 continue;
             }
 

crates/editor/src/display_map/inlay_map.rs 🔗

@@ -1,10 +1,8 @@
-use crate::{
-    multi_buffer::{MultiBufferChunks, MultiBufferRows},
-    Anchor, InlayId, MultiBufferSnapshot, ToOffset,
-};
+use crate::{Anchor, InlayId, MultiBufferSnapshot, ToOffset};
 use collections::{BTreeMap, BTreeSet};
 use gpui::fonts::HighlightStyle;
 use language::{Chunk, Edit, Point, TextSummary};
+use multi_buffer::{MultiBufferChunks, MultiBufferRows};
 use std::{
     any::TypeId,
     cmp,

crates/editor/src/editor.rs 🔗

@@ -11,7 +11,6 @@ pub mod items;
 mod link_go_to_definition;
 mod mouse_context_menu;
 pub mod movement;
-pub mod multi_buffer;
 mod persistence;
 pub mod scroll;
 pub mod selections_collection;
@@ -7716,8 +7715,8 @@ impl Editor {
                     let mut buffer_highlights = this
                         .document_highlights_for_position(selection.head(), &buffer)
                         .filter(|highlight| {
-                            highlight.start.excerpt_id() == selection.head().excerpt_id()
-                                && highlight.end.excerpt_id() == selection.head().excerpt_id()
+                            highlight.start.excerpt_id == selection.head().excerpt_id
+                                && highlight.end.excerpt_id == selection.head().excerpt_id
                         });
                     buffer_highlights
                         .next()

crates/editor/src/git.rs 🔗

@@ -87,3 +87,196 @@ pub fn diff_hunk_to_display(hunk: DiffHunk<u32>, snapshot: &DisplaySnapshot) ->
         }
     }
 }
+
+#[cfg(any(test, feature = "test_support"))]
+mod tests {
+    use crate::editor_tests::init_test;
+    use crate::Point;
+    use gpui::TestAppContext;
+    use multi_buffer::{ExcerptRange, MultiBuffer};
+    use project::{FakeFs, Project};
+    use unindent::Unindent;
+    #[gpui::test]
+    async fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
+        use git::diff::DiffHunkStatus;
+        init_test(cx, |_| {});
+
+        let fs = FakeFs::new(cx.background());
+        let project = Project::test(fs, [], cx).await;
+
+        // buffer has two modified hunks with two rows each
+        let buffer_1 = project
+            .update(cx, |project, cx| {
+                project.create_buffer(
+                    "
+                        1.zero
+                        1.ONE
+                        1.TWO
+                        1.three
+                        1.FOUR
+                        1.FIVE
+                        1.six
+                    "
+                    .unindent()
+                    .as_str(),
+                    None,
+                    cx,
+                )
+            })
+            .unwrap();
+        buffer_1.update(cx, |buffer, cx| {
+            buffer.set_diff_base(
+                Some(
+                    "
+                        1.zero
+                        1.one
+                        1.two
+                        1.three
+                        1.four
+                        1.five
+                        1.six
+                    "
+                    .unindent(),
+                ),
+                cx,
+            );
+        });
+
+        // buffer has a deletion hunk and an insertion hunk
+        let buffer_2 = project
+            .update(cx, |project, cx| {
+                project.create_buffer(
+                    "
+                        2.zero
+                        2.one
+                        2.two
+                        2.three
+                        2.four
+                        2.five
+                        2.six
+                    "
+                    .unindent()
+                    .as_str(),
+                    None,
+                    cx,
+                )
+            })
+            .unwrap();
+        buffer_2.update(cx, |buffer, cx| {
+            buffer.set_diff_base(
+                Some(
+                    "
+                        2.zero
+                        2.one
+                        2.one-and-a-half
+                        2.two
+                        2.three
+                        2.four
+                        2.six
+                    "
+                    .unindent(),
+                ),
+                cx,
+            );
+        });
+
+        cx.foreground().run_until_parked();
+
+        let multibuffer = cx.add_model(|cx| {
+            let mut multibuffer = MultiBuffer::new(0);
+            multibuffer.push_excerpts(
+                buffer_1.clone(),
+                [
+                    // excerpt ends in the middle of a modified hunk
+                    ExcerptRange {
+                        context: Point::new(0, 0)..Point::new(1, 5),
+                        primary: Default::default(),
+                    },
+                    // excerpt begins in the middle of a modified hunk
+                    ExcerptRange {
+                        context: Point::new(5, 0)..Point::new(6, 5),
+                        primary: Default::default(),
+                    },
+                ],
+                cx,
+            );
+            multibuffer.push_excerpts(
+                buffer_2.clone(),
+                [
+                    // excerpt ends at a deletion
+                    ExcerptRange {
+                        context: Point::new(0, 0)..Point::new(1, 5),
+                        primary: Default::default(),
+                    },
+                    // excerpt starts at a deletion
+                    ExcerptRange {
+                        context: Point::new(2, 0)..Point::new(2, 5),
+                        primary: Default::default(),
+                    },
+                    // excerpt fully contains a deletion hunk
+                    ExcerptRange {
+                        context: Point::new(1, 0)..Point::new(2, 5),
+                        primary: Default::default(),
+                    },
+                    // excerpt fully contains an insertion hunk
+                    ExcerptRange {
+                        context: Point::new(4, 0)..Point::new(6, 5),
+                        primary: Default::default(),
+                    },
+                ],
+                cx,
+            );
+            multibuffer
+        });
+
+        let snapshot = multibuffer.read_with(cx, |b, cx| b.snapshot(cx));
+
+        assert_eq!(
+            snapshot.text(),
+            "
+                1.zero
+                1.ONE
+                1.FIVE
+                1.six
+                2.zero
+                2.one
+                2.two
+                2.one
+                2.two
+                2.four
+                2.five
+                2.six"
+                .unindent()
+        );
+
+        let expected = [
+            (DiffHunkStatus::Modified, 1..2),
+            (DiffHunkStatus::Modified, 2..3),
+            //TODO: Define better when and where removed hunks show up at range extremities
+            (DiffHunkStatus::Removed, 6..6),
+            (DiffHunkStatus::Removed, 8..8),
+            (DiffHunkStatus::Added, 10..11),
+        ];
+
+        assert_eq!(
+            snapshot
+                .git_diff_hunks_in_range(0..12)
+                .map(|hunk| (hunk.status(), hunk.buffer_range))
+                .collect::<Vec<_>>(),
+            &expected,
+        );
+
+        assert_eq!(
+            snapshot
+                .git_diff_hunks_in_range_rev(0..12)
+                .map(|hunk| (hunk.status(), hunk.buffer_range))
+                .collect::<Vec<_>>(),
+            expected
+                .iter()
+                .rev()
+                .cloned()
+                .collect::<Vec<_>>()
+                .as_slice(),
+        );
+    }
+}

crates/editor/src/test/editor_lsp_test_context.rs 🔗

@@ -6,18 +6,18 @@ use std::{
 
 use anyhow::Result;
 
+use crate::{Editor, ToPoint};
 use collections::HashSet;
 use futures::Future;
 use gpui::{json, ViewContext, ViewHandle};
 use indoc::indoc;
 use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageQueries};
 use lsp::{notification, request};
+use multi_buffer::ToPointUtf16;
 use project::Project;
 use smol::stream::StreamExt;
 use workspace::{AppState, Workspace, WorkspaceHandle};
 
-use crate::{multi_buffer::ToPointUtf16, Editor, ToPoint};
-
 use super::editor_test_context::EditorTestContext;
 
 pub struct EditorLspTestContext<'a> {

crates/multi_buffer/Cargo.toml 🔗

@@ -0,0 +1,80 @@
+[package]
+name = "multi_buffer"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/multi_buffer.rs"
+doctest = false
+
+[features]
+test-support = [
+    "copilot/test-support",
+    "text/test-support",
+    "language/test-support",
+    "gpui/test-support",
+    "util/test-support",
+    "tree-sitter-rust",
+    "tree-sitter-typescript"
+]
+
+[dependencies]
+client = { path = "../client" }
+clock = { path = "../clock" }
+collections = { path = "../collections" }
+context_menu = { path = "../context_menu" }
+git = { path = "../git" }
+gpui = { path = "../gpui" }
+language = { path = "../language" }
+lsp = { path = "../lsp" }
+rich_text = { path = "../rich_text" }
+settings = { path = "../settings" }
+snippet = { path = "../snippet" }
+sum_tree = { path = "../sum_tree" }
+text = { path = "../text" }
+theme = { path = "../theme" }
+util = { path = "../util" }
+
+aho-corasick = "1.1"
+anyhow.workspace = true
+convert_case = "0.6.0"
+futures.workspace = true
+indoc = "1.0.4"
+itertools = "0.10"
+lazy_static.workspace = true
+log.workspace = true
+ordered-float.workspace = true
+parking_lot.workspace = true
+postage.workspace = true
+pulldown-cmark = { version = "0.9.2", default-features = false }
+rand.workspace = true
+schemars.workspace = true
+serde.workspace = true
+serde_derive.workspace = true
+smallvec.workspace = true
+smol.workspace = true
+
+tree-sitter-rust = { workspace = true, optional = true }
+tree-sitter-html = { workspace = true, optional = true }
+tree-sitter-typescript = { workspace = true, optional = true }
+
+[dev-dependencies]
+copilot = { path = "../copilot", features = ["test-support"] }
+text = { path = "../text", features = ["test-support"] }
+language = { path = "../language", features = ["test-support"] }
+lsp = { path = "../lsp", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
+util = { path = "../util", features = ["test-support"] }
+project = { path = "../project", features = ["test-support"] }
+settings = { path = "../settings", features = ["test-support"] }
+workspace = { path = "../workspace", features = ["test-support"] }
+
+ctor.workspace = true
+env_logger.workspace = true
+rand.workspace = true
+unindent.workspace = true
+tree-sitter.workspace = true
+tree-sitter-rust.workspace = true
+tree-sitter-html.workspace = true
+tree-sitter-typescript.workspace = true

crates/editor/src/multi_buffer/anchor.rs → crates/multi_buffer/src/anchor.rs 🔗

@@ -8,9 +8,9 @@ use sum_tree::Bias;
 
 #[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
 pub struct Anchor {
-    pub(crate) buffer_id: Option<u64>,
-    pub(crate) excerpt_id: ExcerptId,
-    pub(crate) text_anchor: text::Anchor,
+    pub buffer_id: Option<u64>,
+    pub excerpt_id: ExcerptId,
+    pub text_anchor: text::Anchor,
 }
 
 impl Anchor {
@@ -30,10 +30,6 @@ impl Anchor {
         }
     }
 
-    pub fn excerpt_id(&self) -> ExcerptId {
-        self.excerpt_id
-    }
-
     pub fn cmp(&self, other: &Anchor, snapshot: &MultiBufferSnapshot) -> Ordering {
         let excerpt_id_cmp = self.excerpt_id.cmp(&other.excerpt_id, snapshot);
         if excerpt_id_cmp.is_eq() {

crates/editor/src/multi_buffer.rs → crates/multi_buffer/src/multi_buffer.rs 🔗

@@ -303,7 +303,7 @@ impl MultiBuffer {
         self.snapshot.borrow().clone()
     }
 
-    pub(crate) fn read(&self, cx: &AppContext) -> Ref<MultiBufferSnapshot> {
+    pub fn read(&self, cx: &AppContext) -> Ref<MultiBufferSnapshot> {
         self.sync(cx);
         self.snapshot.borrow()
     }
@@ -589,7 +589,7 @@ impl MultiBuffer {
         self.start_transaction_at(Instant::now(), cx)
     }
 
-    pub(crate) fn start_transaction_at(
+    pub fn start_transaction_at(
         &mut self,
         now: Instant,
         cx: &mut ModelContext<Self>,
@@ -608,7 +608,7 @@ impl MultiBuffer {
         self.end_transaction_at(Instant::now(), cx)
     }
 
-    pub(crate) fn end_transaction_at(
+    pub fn end_transaction_at(
         &mut self,
         now: Instant,
         cx: &mut ModelContext<Self>,
@@ -1508,7 +1508,7 @@ impl MultiBuffer {
         "untitled".into()
     }
 
-    #[cfg(test)]
+    #[cfg(any(test, feature = "test-support"))]
     pub fn is_parsing(&self, cx: &AppContext) -> bool {
         self.as_singleton().unwrap().read(cx).is_parsing()
     }
@@ -3198,7 +3198,7 @@ impl MultiBufferSnapshot {
         theme: Option<&SyntaxTheme>,
     ) -> Option<(u64, Vec<OutlineItem<Anchor>>)> {
         let anchor = self.anchor_before(offset);
-        let excerpt_id = anchor.excerpt_id();
+        let excerpt_id = anchor.excerpt_id;
         let excerpt = self.excerpt(excerpt_id)?;
         Some((
             excerpt.buffer_id,
@@ -4129,17 +4129,13 @@ where
 
 #[cfg(test)]
 mod tests {
-    use crate::editor_tests::init_test;
-
     use super::*;
     use futures::StreamExt;
     use gpui::{AppContext, TestAppContext};
     use language::{Buffer, Rope};
-    use project::{FakeFs, Project};
     use rand::prelude::*;
     use settings::SettingsStore;
     use std::{env, rc::Rc};
-    use unindent::Unindent;
     use util::test::sample_text;
 
     #[gpui::test]
@@ -4838,190 +4834,6 @@ mod tests {
         );
     }
 
-    #[gpui::test]
-    async fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
-        use git::diff::DiffHunkStatus;
-        init_test(cx, |_| {});
-
-        let fs = FakeFs::new(cx.background());
-        let project = Project::test(fs, [], cx).await;
-
-        // buffer has two modified hunks with two rows each
-        let buffer_1 = project
-            .update(cx, |project, cx| {
-                project.create_buffer(
-                    "
-                        1.zero
-                        1.ONE
-                        1.TWO
-                        1.three
-                        1.FOUR
-                        1.FIVE
-                        1.six
-                    "
-                    .unindent()
-                    .as_str(),
-                    None,
-                    cx,
-                )
-            })
-            .unwrap();
-        buffer_1.update(cx, |buffer, cx| {
-            buffer.set_diff_base(
-                Some(
-                    "
-                        1.zero
-                        1.one
-                        1.two
-                        1.three
-                        1.four
-                        1.five
-                        1.six
-                    "
-                    .unindent(),
-                ),
-                cx,
-            );
-        });
-
-        // buffer has a deletion hunk and an insertion hunk
-        let buffer_2 = project
-            .update(cx, |project, cx| {
-                project.create_buffer(
-                    "
-                        2.zero
-                        2.one
-                        2.two
-                        2.three
-                        2.four
-                        2.five
-                        2.six
-                    "
-                    .unindent()
-                    .as_str(),
-                    None,
-                    cx,
-                )
-            })
-            .unwrap();
-        buffer_2.update(cx, |buffer, cx| {
-            buffer.set_diff_base(
-                Some(
-                    "
-                        2.zero
-                        2.one
-                        2.one-and-a-half
-                        2.two
-                        2.three
-                        2.four
-                        2.six
-                    "
-                    .unindent(),
-                ),
-                cx,
-            );
-        });
-
-        cx.foreground().run_until_parked();
-
-        let multibuffer = cx.add_model(|cx| {
-            let mut multibuffer = MultiBuffer::new(0);
-            multibuffer.push_excerpts(
-                buffer_1.clone(),
-                [
-                    // excerpt ends in the middle of a modified hunk
-                    ExcerptRange {
-                        context: Point::new(0, 0)..Point::new(1, 5),
-                        primary: Default::default(),
-                    },
-                    // excerpt begins in the middle of a modified hunk
-                    ExcerptRange {
-                        context: Point::new(5, 0)..Point::new(6, 5),
-                        primary: Default::default(),
-                    },
-                ],
-                cx,
-            );
-            multibuffer.push_excerpts(
-                buffer_2.clone(),
-                [
-                    // excerpt ends at a deletion
-                    ExcerptRange {
-                        context: Point::new(0, 0)..Point::new(1, 5),
-                        primary: Default::default(),
-                    },
-                    // excerpt starts at a deletion
-                    ExcerptRange {
-                        context: Point::new(2, 0)..Point::new(2, 5),
-                        primary: Default::default(),
-                    },
-                    // excerpt fully contains a deletion hunk
-                    ExcerptRange {
-                        context: Point::new(1, 0)..Point::new(2, 5),
-                        primary: Default::default(),
-                    },
-                    // excerpt fully contains an insertion hunk
-                    ExcerptRange {
-                        context: Point::new(4, 0)..Point::new(6, 5),
-                        primary: Default::default(),
-                    },
-                ],
-                cx,
-            );
-            multibuffer
-        });
-
-        let snapshot = multibuffer.read_with(cx, |b, cx| b.snapshot(cx));
-
-        assert_eq!(
-            snapshot.text(),
-            "
-                1.zero
-                1.ONE
-                1.FIVE
-                1.six
-                2.zero
-                2.one
-                2.two
-                2.one
-                2.two
-                2.four
-                2.five
-                2.six"
-                .unindent()
-        );
-
-        let expected = [
-            (DiffHunkStatus::Modified, 1..2),
-            (DiffHunkStatus::Modified, 2..3),
-            //TODO: Define better when and where removed hunks show up at range extremities
-            (DiffHunkStatus::Removed, 6..6),
-            (DiffHunkStatus::Removed, 8..8),
-            (DiffHunkStatus::Added, 10..11),
-        ];
-
-        assert_eq!(
-            snapshot
-                .git_diff_hunks_in_range(0..12)
-                .map(|hunk| (hunk.status(), hunk.buffer_range))
-                .collect::<Vec<_>>(),
-            &expected,
-        );
-
-        assert_eq!(
-            snapshot
-                .git_diff_hunks_in_range_rev(0..12)
-                .map(|hunk| (hunk.status(), hunk.buffer_range))
-                .collect::<Vec<_>>(),
-            expected
-                .iter()
-                .rev()
-                .cloned()
-                .collect::<Vec<_>>()
-                .as_slice(),
-        );
-    }
-
     #[gpui::test(iterations = 100)]
     fn test_random_multibuffer(cx: &mut AppContext, mut rng: StdRng) {
         let operations = env::var("OPERATIONS")