1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
use super::*;
impl<R> UiState<R> {
pub fn try_update_clipboard_from_string<V: RowViewer<R>>(
&mut self,
vwr: &mut V,
contents: &str,
) -> bool {
/*
NOTE: System clipboard implementation
We can't just determine if the internal clipboard should be preferred over the system
clipboard, as the source of clipboard contents can be vary. Therefore, on every copy
operation, we should clear out the system clipboard unconditionally, then we tries to
encode the copied content into clipboard.
TODO: Need to find way to handle if both of system clipboard and internal clipboard
content exist. We NEED to determine if which content should be applied for this.
# Dumping
- For rectangular(including single cell) selection of data, we'll just create
appropriate sized small TSV data which suits within given range.
- Note that this'll differentiate the clipboard behavior from internal-only
version.
- For non-rectangular selections, full-scale rectangular table is dumped which
can cover all selection range including empty selections; where any data that
is not being dumped is just emptied out.
- From this, any data cell that is just 'empty' but selected, should be dumped
as explicit empty data; in this case, empty data wrapped with double
quotes("").
# Decoding
- Every format is regarded as TSV. (only \t, \n matters)
- For TSV data with same column count with this table
- Parse as full-scale table, then put into clipboard as-is.
- Column count is less than current table
- In this case, current selection matters.
- Offset the copied content table as the selection column offset.
- Then create clipboard data from it.
- If column count is larger than this, it is invalid data; we just skip parsing
*/
let Some(mut codec) = vwr.try_create_codec(false) else {
// Even when there is system clipboard content, we're going to ignore it and use
// internal clipboard if there's no way to parse it.
return false;
};
if let CursorState::Select(selections) = &self.cc_cursor {
let Some(first) = selections.first().map(|x| x.0) else {
// No selectgion present. Do nothing
return false;
};
let (.., col) = first.row_col(self.p.vis_cols.len());
col
} else {
// If there's no selection, we'll just ignore the system clipboard input
return false;
};
let selection_offset = if let CursorState::Select(sel) = &self.cc_cursor {
sel.first().map_or(0, |idx| {
let (_, c) = idx.0.row_col(self.p.vis_cols.len());
c.0
})
} else {
0
};
let view = tsv::ParsedTsv::parse(contents);
let table_width = view.calc_table_width();
if table_width > self.p.vis_cols.len() {
// If the copied data has more columns than current table, we'll just ignore it.
return false;
}
// If any cell is failed to be parsed, we'll just give up all parsing then use internal
// clipboard instead.
let mut slab = Vec::new();
let mut pastes = Vec::new();
for (row_offset, row_data) in view.iter_rows() {
let slab_id = slab.len();
slab.push(codec.create_empty_decoded_row());
// The restoration point of pastes stack.
let pastes_restore = pastes.len();
for (column, data) in row_data {
let col_idx = column + selection_offset;
if col_idx > self.p.vis_cols.len() {
// If the column is out of range, we'll just ignore it.
return false;
}
match codec.decode_column(data, col_idx, &mut slab[slab_id]) {
Ok(_) => {
pastes.push((
VisRowOffset(row_offset),
ColumnIdx(col_idx),
RowSlabIndex(slab_id),
));
}
Err(DecodeErrorBehavior::SkipCell) => {
// Skip this cell.
}
Err(DecodeErrorBehavior::SkipRow) => {
pastes.drain(pastes_restore..);
slab.pop();
break;
}
Err(DecodeErrorBehavior::Abort) => {
return false;
}
}
}
}
// Replace the clipboard content from the parsed data.
self.clipboard = Some(Clipboard {
slab: slab.into_boxed_slice(),
pastes: pastes.into_boxed_slice(),
});
true
}
pub(super) fn try_dump_clipboard_content<V: RowViewer<R>>(
clipboard: &Clipboard<R>,
vwr: &mut V,
) -> Option<String> {
// clipboard MUST be sorted before dumping; XXX: add assertion?
#[allow(unused_mut)]
let mut codec = vwr.try_create_codec(true)?;
let mut width = 0;
let mut height = 0;
// We're going to offset the column to the minimum column index to make the selection copy
// more intuitive. If not, the copied data will be shifted to the right if the selection is
// not the very first column.
let mut min_column = usize::MAX;
for (row, column, ..) in clipboard.pastes.iter() {
width = width.max(column.0 + 1);
height = height.max(row.0 + 1);
min_column = min_column.min(column.0);
}
let column_offset = min_column;
let mut buf_out = String::new();
let mut buf_tmp = String::new();
let mut row_cursor = 0;
for (row, columns, ..) in &clipboard.pastes.iter().chunk_by(|(row, ..)| *row) {
while row_cursor < row.0 {
tsv::write_newline(&mut buf_out);
row_cursor += 1;
}
let mut column_cursor = 0;
for (_, column, data_idx) in columns.into_iter() {
while column_cursor < column.0 - column_offset {
tsv::write_tab(&mut buf_out);
column_cursor += 1;
}
let data = &clipboard.slab[data_idx.0];
codec.encode_column(data, column.0, &mut buf_tmp);
tsv::write_content(&mut buf_out, &buf_tmp);
buf_tmp.clear();
}
}
Some(buf_out)
}
}