// Copyright (C) 2014 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.gerrit.client.diff; import com.google.gerrit.client.DiffObject; import com.google.gerrit.client.Gerrit; import com.google.gerrit.client.changes.CommentInfo; import com.google.gerrit.client.diff.LineMapper.LineOnOtherInfo; import com.google.gerrit.client.diff.UnifiedChunkManager.LineRegionInfo; import com.google.gerrit.client.diff.UnifiedChunkManager.RegionType; import com.google.gerrit.client.ui.CommentLinkProcessor; import com.google.gerrit.reviewdb.client.PatchSet; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import net.codemirror.lib.CodeMirror; import net.codemirror.lib.Pos; import net.codemirror.lib.TextMarker.FromTo; /** Tracks comment widgets for {@link Unified}. */ class UnifiedCommentManager extends CommentManager { private final SortedMap<Integer, CommentGroup> mergedMap; // In Unified, a CodeMirror line can have up to two CommentGroups - one for // the base side and one for the revision, so we need to keep track of the // duplicates and replace the entries in mergedMap on draft removal. private final Map<Integer, CommentGroup> duplicates; UnifiedCommentManager( Unified host, DiffObject base, PatchSet.Id revision, String path, CommentLinkProcessor clp, boolean open) { super(host, base, revision, path, clp, open); mergedMap = new TreeMap<>(); duplicates = new HashMap<>(); } @Override SortedMap<Integer, CommentGroup> getMapForNav(DisplaySide side) { return mergedMap; } @Override void clearLine(DisplaySide side, int line, CommentGroup group) { super.clearLine(side, line, group); if (mergedMap.get(line) == group) { mergedMap.remove(line); if (duplicates.containsKey(line)) { mergedMap.put(line, duplicates.remove(line)); } } } @Override void newDraftOnGutterClick(CodeMirror cm, String gutterClass, int cmLinePlusOne) { if (!Gerrit.isSignedIn()) { signInCallback(cm).run(); } else { LineRegionInfo info = ((Unified) host).getLineRegionInfoFromCmLine(cmLinePlusOne - 1); DisplaySide side = gutterClass.equals(UnifiedTable.style.lineNumbersLeft()) ? DisplaySide.A : DisplaySide.B; int line = info.line; if (info.getSide() != side) { line = host.lineOnOther(info.getSide(), line).getLine(); } insertNewDraft(side, line + 1); } } @Override CommentGroup getCommentGroupOnActiveLine(CodeMirror cm) { CommentGroup group = null; if (cm.extras().hasActiveLine()) { int cmLinePlusOne = cm.getLineNumber(cm.extras().activeLine()) + 1; LineRegionInfo info = ((Unified) host).getLineRegionInfoFromCmLine(cmLinePlusOne - 1); CommentGroup forSide = map(info.getSide()).get(cmLinePlusOne); group = forSide == null ? map(info.getSide().otherSide()).get(cmLinePlusOne) : forSide; } return group; } @Override Collection<Integer> getLinesWithCommentGroups() { return mergedMap.tailMap(1).keySet(); } @Override String getTokenSuffixForActiveLine(CodeMirror cm) { int cmLinePlusOne = cm.getLineNumber(cm.extras().activeLine()) + 1; LineRegionInfo info = ((Unified) host).getLineRegionInfoFromCmLine(cmLinePlusOne - 1); return (info.getSide() == DisplaySide.A ? "a" : "") + cmLinePlusOne; } @Override void newDraft(CodeMirror cm) { if (cm.somethingSelected()) { FromTo fromTo = adjustSelection(cm); Pos from = fromTo.from(); Pos to = fromTo.to(); Unified unified = (Unified) host; UnifiedChunkManager manager = unified.getChunkManager(); LineRegionInfo fromInfo = unified.getLineRegionInfoFromCmLine(from.line()); LineRegionInfo toInfo = unified.getLineRegionInfoFromCmLine(to.line()); DisplaySide side = toInfo.getSide(); // Handle special cases in selections that span multiple regions. Force // start line to be on the same side as the end line. if ((fromInfo.type == RegionType.INSERT || fromInfo.type == RegionType.COMMON) && toInfo.type == RegionType.DELETE) { LineOnOtherInfo infoOnSideA = manager.lineMapper.lineOnOther(DisplaySide.B, fromInfo.line); int startLineOnSideA = infoOnSideA.getLine(); if (infoOnSideA.isAligned()) { from.line(startLineOnSideA); } else { from.line(startLineOnSideA + 1); } from.ch(0); to.line(toInfo.line); } else if (fromInfo.type == RegionType.DELETE && toInfo.type == RegionType.INSERT) { LineOnOtherInfo infoOnSideB = manager.lineMapper.lineOnOther(DisplaySide.A, fromInfo.line); int startLineOnSideB = infoOnSideB.getLine(); if (infoOnSideB.isAligned()) { from.line(startLineOnSideB); } else { from.line(startLineOnSideB + 1); } from.ch(0); to.line(toInfo.line); } else if (fromInfo.type == RegionType.DELETE && toInfo.type == RegionType.COMMON) { int toLineOnSideA = manager.lineMapper.lineOnOther(DisplaySide.B, toInfo.line).getLine(); from.line(fromInfo.line); // Force the end line to be on the same side as the start line. to.line(toLineOnSideA); side = DisplaySide.A; } else { // Common case from.line(fromInfo.line); to.line(toInfo.line); } addDraftBox( side, CommentInfo.create( getPath(), getStoredSideFromDisplaySide(side), to.line() + 1, CommentRange.create(fromTo), false)) .setEdit(true); cm.setCursor(Pos.create(host.getCmLine(to.line(), side), to.ch())); cm.setSelection(cm.getCursor()); } else { int cmLine = cm.getLineNumber(cm.extras().activeLine()); LineRegionInfo info = ((Unified) host).getLineRegionInfoFromCmLine(cmLine); insertNewDraft(info.getSide(), cmLine + 1); } } @Override CommentGroup group(DisplaySide side, int cmLinePlusOne) { Map<Integer, CommentGroup> map = map(side); CommentGroup existing = map.get(cmLinePlusOne); if (existing != null) { return existing; } UnifiedCommentGroup g = new UnifiedCommentGroup(this, host.getCmFromSide(side), side, cmLinePlusOne); map.put(cmLinePlusOne, g); if (mergedMap.containsKey(cmLinePlusOne)) { duplicates.put(cmLinePlusOne, mergedMap.remove(cmLinePlusOne)); } mergedMap.put(cmLinePlusOne, g); if (isAttached()) { g.init(host.getDiffTable()); g.handleRedraw(); } return g; } }