|
| 1 | +use biome_deserialize::Text; |
1 | 2 | use pgt_text_size::{TextLen, TextRange, TextSize};
|
2 |
| -use std::ops::{Add, Sub}; |
| 3 | +use std::{ |
| 4 | + i64, |
| 5 | + ops::{Add, Sub}, |
| 6 | +}; |
3 | 7 |
|
4 | 8 | use crate::workspace::{ChangeFileParams, ChangeParams};
|
5 | 9 |
|
@@ -58,26 +62,37 @@ struct Affected {
|
58 | 62 | impl Document {
|
59 | 63 | /// Applies a file change to the document and returns the affected statements
|
60 | 64 | pub fn apply_file_change(&mut self, change: &ChangeFileParams) -> Vec<StatementChange> {
|
61 |
| - tracing::debug!("apply_file_change: {:?}", change); |
62 | 65 | // cleanup all diagnostics with every change because we cannot guarantee that they are still valid
|
63 | 66 | // this is because we know their ranges only by finding slices within the content which is
|
64 | 67 | // very much not guaranteed to result in correct ranges
|
65 | 68 | self.diagnostics.clear();
|
66 | 69 |
|
| 70 | + // when we recieive more than one change, we need to push back the changes based on the |
| 71 | + // total range of the previous ones. This is because the ranges are always related to the original state. |
67 | 72 | let mut changes = Vec::new();
|
68 | 73 |
|
69 |
| - let mut push_back: TextSize = 0.into(); |
| 74 | + let mut offset: i64 = 0; |
70 | 75 |
|
71 | 76 | for change in &change.changes {
|
72 |
| - let change = if push_back > 0.into() { |
73 |
| - &change.push_back(push_back) |
| 77 | + let adjusted_change = if offset != 0 && change.range.is_some() { |
| 78 | + &ChangeParams { |
| 79 | + text: change.text.clone(), |
| 80 | + range: change.range.map(|range| { |
| 81 | + let start = u32::from(range.start()); |
| 82 | + let end = u32::from(range.end()); |
| 83 | + TextRange::new( |
| 84 | + TextSize::from((start as i64 + offset).try_into().unwrap_or(0)), |
| 85 | + TextSize::from((end as i64 + offset).try_into().unwrap_or(0)), |
| 86 | + ) |
| 87 | + }), |
| 88 | + } |
74 | 89 | } else {
|
75 | 90 | change
|
76 | 91 | };
|
77 | 92 |
|
78 |
| - changes.extend(self.apply_change(change)); |
| 93 | + changes.extend(self.apply_change(adjusted_change)); |
79 | 94 |
|
80 |
| - push_back += change.diff_size(); |
| 95 | + offset += change.change_size(); |
81 | 96 | }
|
82 | 97 |
|
83 | 98 | self.version = change.version;
|
@@ -367,6 +382,18 @@ impl Document {
|
367 | 382 | }
|
368 | 383 |
|
369 | 384 | impl ChangeParams {
|
| 385 | + /// For lack of a better name, this returns the change in size of the text compared to the range |
| 386 | + pub fn change_size(&self) -> i64 { |
| 387 | + match self.range { |
| 388 | + Some(range) => { |
| 389 | + let range_length: usize = range.len().into(); |
| 390 | + let text_length = self.text.chars().count(); |
| 391 | + text_length as i64 - range_length as i64 |
| 392 | + } |
| 393 | + None => i64::try_from(self.text.chars().count()).unwrap(), |
| 394 | + } |
| 395 | + } |
| 396 | + |
370 | 397 | pub fn diff_size(&self) -> TextSize {
|
371 | 398 | match self.range {
|
372 | 399 | Some(range) => {
|
@@ -1534,7 +1561,39 @@ mod tests {
|
1534 | 1561 | }
|
1535 | 1562 |
|
1536 | 1563 | #[test]
|
1537 |
| - fn multiple_changes_at_once() { |
| 1564 | + fn multiple_deletions_at_once() { |
| 1565 | + let path = PgTPath::new("test.sql"); |
| 1566 | + |
| 1567 | + let mut doc = Document::new("\n\n\n\nALTER TABLE ONLY \"public\".\"sendout\"\n ADD CONSTRAINT \"sendout_organisation_id_fkey\" FOREIGN |
| 1568 | +KEY (\"organisation_id\") REFERENCES \"public\".\"organisation\"(\"id\") ON UPDATE RESTRICT ON DELETE CASCADE;\n".to_string(), 0); |
| 1569 | + |
| 1570 | + let change = ChangeFileParams { |
| 1571 | + path: path.clone(), |
| 1572 | + version: 1, |
| 1573 | + changes: vec![ |
| 1574 | + ChangeParams { |
| 1575 | + range: Some(TextRange::new(31.into(), 38.into())), |
| 1576 | + text: "te".to_string(), |
| 1577 | + }, |
| 1578 | + ChangeParams { |
| 1579 | + range: Some(TextRange::new(60.into(), 67.into())), |
| 1580 | + text: "te".to_string(), |
| 1581 | + }, |
| 1582 | + ], |
| 1583 | + }; |
| 1584 | + |
| 1585 | + let changed = doc.apply_file_change(&change); |
| 1586 | + |
| 1587 | + assert_eq!(doc.content, "\n\n\n\nALTER TABLE ONLY \"public\".\"te\"\n ADD CONSTRAINT \"te_organisation_id_fkey\" FOREIGN |
| 1588 | +KEY (\"organisation_id\") REFERENCES \"public\".\"organisation\"(\"id\") ON UPDATE RESTRICT ON DELETE CASCADE;\n"); |
| 1589 | + |
| 1590 | + assert_eq!(changed.len(), 2); |
| 1591 | + |
| 1592 | + assert_document_integrity(&doc); |
| 1593 | + } |
| 1594 | + |
| 1595 | + #[test] |
| 1596 | + fn multiple_additions_at_once() { |
1538 | 1597 | let path = PgTPath::new("test.sql");
|
1539 | 1598 |
|
1540 | 1599 | let mut doc = Document::new("\n\n\n\nALTER TABLE ONLY \"public\".\"sendout\"\n ADD CONSTRAINT \"sendout_organisation_id_fkey\" FOREIGN
|
|
0 commit comments