add_ed/cmd/editing_commands/
input.rs

1use super::*;
2
3fn inner_input(
4  state: &mut Ed<'_>,
5  full_command: &str,
6  mut input: Vec<String>,
7  index: usize,
8) -> Result<()> {
9  let buffer = state.history.current_mut(full_command.into());
10  let mut tail = buffer.split_off(index);
11  for line in input.drain(..) {
12    buffer.push(Line::new(line).map_err(InternalError::InvalidLineText)?);
13  }
14  buffer.append(&mut tail);
15  Ok(())
16}
17enum InlineSide {
18  Before,
19  After,
20}
21fn inner_inline_input(
22  state: &mut Ed<'_>,
23  full_command: &str,
24  mut input: Vec<String>,
25  line: usize,
26  side: InlineSide,
27) -> Result<()> {
28  let buffer = state.history.current_mut(full_command.into());
29  let mut tail = buffer.split_off(line);
30  let indexed_line = buffer.split_off(line - 1);
31  // We need to verify this here, so we can unwrap from the iterator later
32  if input.len() == 0 { return Err(EdError::NoOp); }
33  let mut input_iter = input.drain(..);
34  // Construct the joined line and insert lines
35  // (order based on which side we inline insert on)
36  match side {
37    InlineSide::Before => {
38      // Insert lines from data first, then indexed line joined with last
39      let mut joined_line = input_iter.next_back().unwrap();
40      joined_line.pop(); // Remove newline that should terminate all lines
41      joined_line.push_str(&indexed_line[0].text[..]);
42      for line in input_iter {
43        buffer.push(Line::new(line).map_err(InternalError::InvalidLineText)?);
44      }
45      // Send in the line itself
46      // Arguably we could use the same tag and matched as from the indexed line
47      // but we don't since that would be inconsistent with 'c' and 'C' full_command.
48      buffer.push(Line::new(joined_line)
49        .map_err(InternalError::InvalidLineText)?
50      );
51    },
52    InlineSide::After => {
53      // Insert indexed line joined with first, then lines from data
54      let mut joined_line = (&indexed_line[0].text[..]).to_owned();
55      joined_line.pop(); // Remove newline that should terminate all lines
56      joined_line.push_str(&input_iter.next().unwrap());
57      // Arguably we could use the same tag and matched as from the indexed line
58      // but we don't since that would be inconsistent with 'c' and 'C' full_command.
59      buffer.push(Line::new(joined_line)
60        .map_err(InternalError::InvalidLineText)?
61      );
62      for line in input_iter {
63        buffer.push(Line::new(line).map_err(InternalError::InvalidLineText)?);
64      }
65    },
66  }
67  buffer.append(&mut tail);
68  state.clipboard = (&*indexed_line).into();
69  Ok(())
70}
71pub fn input(
72  state: &mut Ed<'_>,
73  ui: &mut dyn UI,
74  pflags: &mut PrintingFlags,
75  full_command: &str,
76  selection: Option<Sel<'_>>,
77  command: char,
78  flags: &str,
79) -> Result<()> {
80  let mut flags = parse_flags(flags, "pnl")?;
81  pflags.p = flags.remove(&'p').unwrap();
82  pflags.n = flags.remove(&'n').unwrap();
83  pflags.l = flags.remove(&'l').unwrap();
84
85  let buffer = state.history.current();
86  let index = match command {
87    'a' | 'A' => {
88      let i = interpret_index_from_selection(&state, selection, state.selection, true)?;
89      if command == 'a' { buffer.verify_index(i)? } else { buffer.verify_line(i)? }
90      i
91    },
92    // Note that saturating_sub really is needed, since inserting at index 0
93    // should be valid and equivalent to inserting at index 1.
94    'i' | 'I' => {
95      let mut i = interpret_index_from_selection(&state, selection, state.selection, false)?;
96      if command == 'i' {
97        i = i.saturating_sub(1);
98        buffer.verify_index(i)?;
99      }
100      else { buffer.verify_line(i)? }
101      i
102    },
103    _ => ed_unreachable!()?,
104  };
105  // Now that we have checked that the command is valid, get input
106  // This is done so we don't drop text input, which would be annoying
107  let input = ui.get_input(
108    state,
109    '.',
110    #[cfg(feature = "initial_input_data")]
111    None,
112  )?;
113  // Run the actual command and save returned selection to state
114  // TODO: replace this post-execution selection prediction with returns from
115  // the inner functions.
116  state.selection = if !input.is_empty() {
117    let start = index + 1; // since buffer.insert puts input after index
118    let end = start + input.len() - 1; // Subtract for inclusive select
119    // In the case of 'a', 'i' that is all
120    // 'A' and 'I' need a join
121    match command {
122      'A' => {
123        inner_inline_input(state, full_command, input, index, InlineSide::After)?;
124        // This offsets start and end of sel by -1
125        (start - 1, end - 1)
126      },
127      'I' => {
128        inner_inline_input(state, full_command, input, index, InlineSide::Before)?;
129        (start - 1,end - 1)
130      },
131      'a' | 'i' => {
132        inner_input(state, full_command, input, index)?;
133        (start, end)
134      },
135      _ => ed_unreachable!()?,
136    }
137  }
138  // If no input is given, keep old selection
139  else {
140    state.selection
141  };
142  Ok(())
143}