org.sonarsource.sonarlint.ls.notebooks.VersionedOpenNotebook Maven / Gradle / Ivy
/*
* SonarLint Language Server
* Copyright (C) 2009-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonarsource.sonarlint.ls.notebooks;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.eclipse.lsp4j.NotebookDocumentChangeEvent;
import org.eclipse.lsp4j.NotebookDocumentChangeEventCellTextContent;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextDocumentItem;
import org.sonarsource.sonarlint.core.commons.api.SonarLanguage;
import org.sonarsource.sonarlint.core.rpc.protocol.client.issue.FileEditDto;
import org.sonarsource.sonarlint.core.rpc.protocol.client.issue.QuickFixDto;
import org.sonarsource.sonarlint.core.rpc.protocol.client.issue.TextEditDto;
import org.sonarsource.sonarlint.core.rpc.protocol.common.TextRangeDto;
import org.sonarsource.sonarlint.ls.connected.DelegatingFinding;
import org.sonarsource.sonarlint.ls.file.VersionedOpenFile;
import static org.sonarsource.sonarlint.ls.notebooks.NotebookUtils.applyChangeToCellContent;
import static org.sonarsource.sonarlint.ls.notebooks.NotebookUtils.fileTextRangeToCellTextRange;
public class VersionedOpenNotebook {
static final String SONAR_PYTHON_NOTEBOOK_CELL_DELIMITER = "#SONAR_PYTHON_NOTEBOOK_CELL_DELIMITER\n";
private final URI uri;
private Integer notebookVersion;
private final LinkedHashMap cells = new LinkedHashMap<>();
private final List orderedCells = new ArrayList<>();
private final Map fileLineToCell = new HashMap<>();
private final Map virtualFileLineToCellLine = new HashMap<>();
private final NotebookDiagnosticPublisher notebookDiagnosticPublisher;
private VersionedOpenNotebook(URI uri, int version, List cells, NotebookDiagnosticPublisher notebookDiagnosticPublisher) {
this.uri = uri;
this.notebookVersion = version;
cells.forEach(cell -> {
this.cells.put(cell.getUri(), cell);
this.orderedCells.add(cell);
});
this.notebookDiagnosticPublisher = notebookDiagnosticPublisher;
}
private void indexCellsByLineNumber() {
var lineCount = 1;
var cellCount = 1;
for (var cell : orderedCells) {
var cellLines = cell.getText().split("\n", -1);
for (var cellLineCount = 1; cellLineCount <= cellLines.length; cellLineCount++) {
fileLineToCell.put(lineCount, cell);
virtualFileLineToCellLine.put(lineCount, cellLineCount);
lineCount++;
if (cellLineCount == cellLines.length && cellCount < orderedCells.size()) {
fileLineToCell.put(lineCount, cell);
virtualFileLineToCellLine.put(lineCount, cellLineCount);
lineCount++;
}
}
cellCount++;
}
}
public static VersionedOpenNotebook create(URI baseUri, int version, List cells, NotebookDiagnosticPublisher notebookDiagnosticPublisher) {
return new VersionedOpenNotebook(baseUri, version, cells, notebookDiagnosticPublisher);
}
public URI getUri() {
return uri;
}
public VersionedOpenFile asVersionedOpenFile() {
return new VersionedOpenFile(uri, SonarLanguage.IPYTHON.getSonarLanguageKey(), this.notebookVersion, getContent());
}
String getContent() {
return orderedCells.stream().map(TextDocumentItem::getText)
.collect(Collectors.joining("\n" + SONAR_PYTHON_NOTEBOOK_CELL_DELIMITER));
}
public int getNotebookVersion() {
return this.notebookVersion;
}
public Set getCellUris() {
return cells.keySet();
}
public Optional getCellUri(int lineNumber) {
return Optional.ofNullable(fileLineToCell.get(lineNumber))
.map(TextDocumentItem::getUri)
.map(URI::create);
}
public DelegatingCellIssue toCellIssue(DelegatingFinding issue) {
indexCellsByLineNumber();
var issueTextRange = issue.getTextRange();
var originalQuickFixes = issue.quickFixes();
var convertedQuickFixes = new ArrayList();
TextRangeDto cellTextRange = null;
if (issueTextRange != null) {
cellTextRange = fileTextRangeToCellTextRange(issueTextRange.getStartLine(), issueTextRange.getStartLineOffset(),
issueTextRange.getEndLine(), issueTextRange.getEndLineOffset(), virtualFileLineToCellLine);
}
if (originalQuickFixes != null && !originalQuickFixes.isEmpty()) {
AtomicReference textEditCellUri = new AtomicReference<>();
for (QuickFixDto quickFix : originalQuickFixes) {
var newFileEdits = quickFix.fileEdits().stream().map(fileEdit -> {
var newTextEdits = fileEdit.textEdits().stream().map(textEdit -> {
textEditCellUri.set(URI.create(fileLineToCell.get(textEdit.range().getStartLine()).getUri()));
var newTextRange = fileTextRangeToCellTextRange(textEdit.range().getStartLine(), textEdit.range().getStartLineOffset(),
textEdit.range().getEndLine(), textEdit.range().getEndLineOffset(), virtualFileLineToCellLine);
return new TextEditDto(newTextRange, textEdit.newText());
}).toList();
return new FileEditDto(textEditCellUri.get(), newTextEdits);
}).toList();
var convertedQuickFix = new QuickFixDto(newFileEdits, quickFix.message());
convertedQuickFixes.add(convertedQuickFix);
}
}
return new DelegatingCellIssue(issue.getFinding(), this.getUri(), cellTextRange, convertedQuickFixes);
}
public void didChange(int version, NotebookDocumentChangeEvent changeEvent) {
this.notebookVersion = version;
if (changeEvent.getCells() != null && changeEvent.getCells().getStructure() != null && !changeEvent.getCells().getStructure().getDidClose().isEmpty()) {
handleCellDeletion(changeEvent.getCells().getStructure().getDidClose());
}
if (changeEvent.getCells() != null && changeEvent.getCells().getStructure() != null && !changeEvent.getCells().getStructure().getDidOpen().isEmpty()) {
handleCellCreation(changeEvent);
}
if (changeEvent.getCells() != null && changeEvent.getCells().getTextContent() != null && !changeEvent.getCells().getTextContent().isEmpty()) {
handleContentChange(changeEvent.getCells().getTextContent());
}
}
private void handleCellDeletion(List removedCellIdentifiers) {
removedCellIdentifiers.forEach(removedCell -> {
var removedItem = cells.remove(removedCell.getUri());
if (removedItem != null) {
orderedCells.remove(removedItem);
notebookDiagnosticPublisher.removeCellDiagnostics(URI.create(removedItem.getUri()));
}
});
}
private void handleCellCreation(NotebookDocumentChangeEvent changeEvent) {
var insertionStart = new AtomicInteger(changeEvent.getCells().getStructure().getArray().getStart());
changeEvent.getCells().getStructure().getDidOpen().forEach(newCell -> {
cells.put(newCell.getUri(), newCell);
orderedCells.add(insertionStart.getAndIncrement(), newCell);
});
}
private void handleContentChange(List textContents) {
textContents.forEach(textContent -> {
var changedCellUri = textContent.getDocument().getUri();
var cell = cells.get(changedCellUri);
cell.setVersion(textContent.getDocument().getVersion());
cell.setText(applyChangeToCellContent(cell, textContent.getChanges()));
});
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy