<commit_before>package com.intellij.codeInsight.lookup.impl;

import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.codeInsight.completion.CompletionPreferencePolicy;
import com.intellij.codeInsight.completion.PrefixMatcher;
import com.intellij.codeInsight.completion.impl.CamelHumpMatcher;
import com.intellij.codeInsight.hint.HintManagerImpl;
import com.intellij.codeInsight.hint.HintUtil;
import com.intellij.codeInsight.lookup.*;
import com.intellij.featureStatistics.FeatureUsageTracker;
import com.intellij.lang.LangBundle;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.event.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.proximity.PsiProximityComparator;
import com.intellij.ui.LightweightHint;
import com.intellij.ui.ListScrollingUtil;
import com.intellij.ui.plaf.beg.BegPopupMenuBorder;
import com.intellij.ui.popup.PopupIcons;
import com.intellij.util.CollectConsumer;
import com.intellij.util.SmartList;
import com.intellij.util.containers.SortedList;
import com.intellij.util.ui.AsyncProcessIcon;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;

import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.List;

public class LookupImpl extends LightweightHint implements Lookup, Disposable {
  private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.lookup.impl.LookupImpl");
  private static final int MAX_PREFERRED_COUNT = 5;

  private static final LookupItem EMPTY_LOOKUP_ITEM = new LookupItem("preselect", "preselect");

  private final Project myProject;
  private final Editor myEditor;
  private final SortedList<LookupElement> myItems;
  private final SortedMap<LookupItemWeightComparable, SortedList<LookupElement>> myItemsMap;
  private final Map<LookupElement, Collection<LookupElementAction>> myItemActions = new THashMap<LookupElement, Collection<LookupElementAction>>();
  private int myMinPrefixLength;
  private int myPreferredItemsCount;
  private int myInitialOffset;
  private long myShownStamp = -1;
  private String myInitialPrefix;
  @Nullable private final LookupItemPreferencePolicy myItemPreferencePolicy;

  private RangeMarker myLookupStartMarker;
  private final JList myList;
  private final LookupCellRenderer myCellRenderer;
  private Boolean myPositionedAbove = null;

  private CaretListener myEditorCaretListener;
  private SelectionListener myEditorSelectionListener;
  private EditorMouseListener myEditorMouseListener;

  private final ArrayList<LookupListener> myListeners = new ArrayList<LookupListener>();

  private boolean myDisposed = false;
  private boolean myHidden = false;
  private LookupElement myPreselectedItem = EMPTY_LOOKUP_ITEM;
  private boolean myDirty;
  private String myAdditionalPrefix = "";
  private final PsiElement myElement;
  private final AsyncProcessIcon myProcessIcon;
  private final Comparator<LookupElement> myComparator;
  private volatile boolean myCalculating;
  private final JLabel myAdComponent;
  private volatile String myAdText;
  private volatile int myLookupWidth = 50;

  public LookupImpl(Project project, Editor editor, LookupElement[] items, @Nullable LookupItemPreferencePolicy itemPreferencePolicy){
    super(new JPanel(new BorderLayout()));
    myProject = project;
    myEditor = editor;
    myItemPreferencePolicy = itemPreferencePolicy;
    myInitialOffset = myEditor.getSelectionModel().hasSelection() ? myEditor.getSelectionModel().getSelectionStart() : myEditor.getCaretModel().getOffset();

    final Document document = myEditor.getDocument();
    final PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(document);
    myElement = psiFile == null ? null : psiFile.findElementAt(myEditor.getCaretModel().getOffset());

    final PsiProximityComparator proximityComparator = new PsiProximityComparator(myElement == null ? psiFile : myElement);
    myComparator = new Comparator<LookupElement>() {
      public int compare(LookupElement o1, LookupElement o2) {
        if (o1 instanceof LookupItem && o2 instanceof LookupItem) {
          double priority1 = ((LookupItem)o1).getPriority();
          double priority2 = ((LookupItem)o2).getPriority();
          if (priority1 > priority2) return -1;
          if (priority2 > priority1) return 1;

          int grouping1 = ((LookupItem)o1).getGrouping();
          int grouping2 = ((LookupItem)o2).getGrouping();
          if (grouping1 > grouping2) return -1;
          if (grouping2 > grouping1) return 1;
        }

        int stringCompare = o1.getLookupString().compareToIgnoreCase(o2.getLookupString());
        if (stringCompare != 0) return stringCompare;

        return proximityComparator.compare(o1.getObject(), o2.getObject());
      }
    };
    myItems = new SortedList<LookupElement>(myComparator);
    myItemsMap = new TreeMap<LookupItemWeightComparable, SortedList<LookupElement>>();

    myProcessIcon = new AsyncProcessIcon("Completion progress");
    myProcessIcon.setVisible(false);
    myList = new JList(new DefaultListModel());
    myCellRenderer = new LookupCellRenderer(this);
    myList.setCellRenderer(myCellRenderer);

    for (final LookupElement item : items) {
      addItem(item);
    }

    myList.setFocusable(false);

    myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    myList.setBackground(LookupCellRenderer.BACKGROUND_COLOR);

    JScrollPane scrollPane = new JScrollPane(myList);
    scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
    getComponent().add(scrollPane, BorderLayout.NORTH);
    scrollPane.setBorder(null);

    JPanel bottomPanel = new JPanel(new BorderLayout());

    bottomPanel.add(myProcessIcon, BorderLayout.EAST);
    myAdComponent = HintUtil.createAdComponent(null);
    bottomPanel.add(myAdComponent, BorderLayout.CENTER);
    getComponent().add(bottomPanel, BorderLayout.SOUTH);
    getComponent().setBorder(new BegPopupMenuBorder());

    updateList();
    selectMostPreferableItem();
  }

  public AsyncProcessIcon getProcessIcon() {
    return myProcessIcon;
  }

  public boolean isCalculating() {
    return myCalculating;
  }

  public void setCalculating(final boolean calculating) {
    myCalculating = calculating;
  }

  public int getPreferredItemsCount() {
    return myPreferredItemsCount;
  }

  public void markDirty() {
    myDirty = true;
    myPreselectedItem = null;
  }

  @TestOnly
  public void resort() {
    final ArrayList<LookupElement> items = new ArrayList<LookupElement>(myItems);
    myDirty = false;
    myPreselectedItem = EMPTY_LOOKUP_ITEM;
    myItemsMap.clear();
    myItems.clear();
    for (final LookupElement item : items) {
      addItem(item);
    }
    updateList();
  }

  public void addItem(LookupElement item) {
    synchronized (myItems) {
      myItems.add(item);
      addItemWeight(item);

      final CollectConsumer<LookupElementAction> consumer = new CollectConsumer<LookupElementAction>();
      for (LookupActionProvider provider : LookupActionProvider.EP_NAME.getExtensions()) {
        provider.fillActions(item, this, consumer);
      }
      myItemActions.put(item, consumer.getResult());
    }
    int maxWidth = myCellRenderer.updateMaximumWidth(item);
    myLookupWidth = Math.max(maxWidth, myLookupWidth);
  }

  public Collection<LookupElementAction> getActionsFor(LookupElement element) {
    synchronized (myItems) {
      final Collection<LookupElementAction> collection = myItemActions.get(element);
      return collection == null ? Collections.<LookupElementAction>emptyList() : collection;
    }
  }

  private void addItemWeight(final LookupElement item) {
    final Comparable[] weight = getWeight(myItemPreferencePolicy, myElement, item);
    final LookupItemWeightComparable key = new LookupItemWeightComparable(item instanceof LookupItem ? ((LookupItem)item).getPriority() : 0, weight);
    SortedList<LookupElement> list = myItemsMap.get(key);
    if (list == null) myItemsMap.put(key, list = new SortedList<LookupElement>(myComparator));
    list.add(item);
  }

  @Nullable
  public LookupItemPreferencePolicy getItemPreferencePolicy() {
    return myItemPreferencePolicy;
  }

  private static Comparable[] getWeight(final LookupItemPreferencePolicy itemPreferencePolicy, final PsiElement context,
                                        final LookupElement item) {
    if (itemPreferencePolicy instanceof CompletionPreferencePolicy) {
      return ((CompletionPreferencePolicy)itemPreferencePolicy).getWeight(item);
    }
    Comparable i = null;
    if (item.getObject() instanceof PsiElement) {
      i = PsiProximityComparator.getProximity((PsiElement)item.getObject(), context);
    }
    return new Comparable[]{i};
  }

  public int getMinPrefixLength() {
    return myMinPrefixLength;
  }

  public JList getList() {
    return myList;
  }

  public LookupElement[] getItems(){
    synchronized (myItems) {
      return myItems.toArray(new LookupElement[myItems.size()]);
    }
  }

  public void setAdvertisementText(@Nullable String text) {
    myAdText = text;
  }

  public String getAdvertisementText() {
    return myAdText;
  }


  public String getAdditionalPrefix() {
    return myAdditionalPrefix;
  }

  public void setAdditionalPrefix(final String additionalPrefix) {
    myAdditionalPrefix = additionalPrefix;
    myInitialPrefix = null;
    markDirty();
    updateList();
  }

  public final void updateList() {
    synchronized (myItems) {
      int minPrefixLength = myItems.isEmpty() ? 0 : Integer.MAX_VALUE;
      for (final LookupElement item : myItems) {
        minPrefixLength = Math.min(item.getPrefixMatcher().getPrefix().length(), minPrefixLength);
      }
      if (myMinPrefixLength != minPrefixLength) {
        myLookupStartMarker = null;
      }
      myMinPrefixLength = minPrefixLength;

      Object oldSelected = !myDirty ? null : myList.getSelectedValue();
      DefaultListModel model = (DefaultListModel)myList.getModel();
      model.clear();

      Set<LookupElement> firstItems = new THashSet<LookupElement>();

      addExactPrefixItems(model, firstItems);

      boolean hasExactPrefixes = !firstItems.isEmpty();

      addMostRelevantItems(model, firstItems);

      final boolean hasPreselectedItem = addPreselectedItem(model, firstItems);

      myPreferredItemsCount = firstItems.size();

      addRemainingItemsLexicographically(model, firstItems);

      boolean isEmpty = model.getSize() == 0;
      if (isEmpty) {
        addEmptyItem(model);
      } else {
        myList.setFixedCellWidth(myLookupWidth);
      }
      myList.setFixedCellHeight(myCellRenderer.getListCellRendererComponent(myList, myList.getModel().getElementAt(0), 0, false, false).getPreferredSize().height);

      myList.setVisibleRowCount(Math.min(myList.getModel().getSize(), CodeInsightSettings.getInstance().LOOKUP_HEIGHT));

      myAdComponent.setText(myAdText);

      if (!isEmpty) {
        if (oldSelected != null) {
          if (hasExactPrefixes || !ListScrollingUtil.selectItem(myList, oldSelected)) {
            selectMostPreferableItem();
          }
        }
        else {
          if (myPreselectedItem == EMPTY_LOOKUP_ITEM) {
            selectMostPreferableItem();
            myPreselectedItem = getCurrentItem();
          }
          else if (hasPreselectedItem && !hasExactPrefixes) {
            ListScrollingUtil.selectItem(myList, myPreselectedItem);
          }
          else {
            selectMostPreferableItem();
          }
        }
      }
    }
  }

  private void addEmptyItem(DefaultListModel model) {
    LookupItem<String> item = new EmptyLookupItem(myCalculating ? " " : LangBundle.message("completion.no.suggestions"));
    item.setPrefixMatcher(new CamelHumpMatcher(""));
    if (!myCalculating) {
      final int maxWidth = myCellRenderer.updateMaximumWidth(item);
      myList.setFixedCellWidth(Math.max(maxWidth, myLookupWidth));
    }

    model.addElement(item);
  }

  private void addRemainingItemsLexicographically(DefaultListModel model, Set<LookupElement> firstItems) {
    for (LookupElement item : myItems) {
      if (!firstItems.contains(item) && prefixMatches(item)) {
        model.addElement(item);
      }
    }
  }

  private boolean addPreselectedItem(DefaultListModel model, Set<LookupElement> firstItems) {
    final boolean hasPreselectedItem = !myDirty && myPreselectedItem != EMPTY_LOOKUP_ITEM;
    if (hasPreselectedItem && !firstItems.contains(myPreselectedItem)) {
      firstItems.add(myPreselectedItem);
      model.addElement(myPreselectedItem);
    }
    return hasPreselectedItem;
  }

  private void addMostRelevantItems(DefaultListModel model, Set<LookupElement> firstItems) {
    for (final LookupItemWeightComparable comparable : myItemsMap.keySet()) {
      final List<LookupElement> suitable = new SmartList<LookupElement>();
      for (final LookupElement item : myItemsMap.get(comparable)) {
        if (!firstItems.contains(item) && prefixMatches(item)) {
          suitable.add(item);
        }
      }

      if (firstItems.size() + suitable.size() > MAX_PREFERRED_COUNT) break;
      for (final LookupElement item : suitable) {
        firstItems.add(item);
        model.addElement(item);
      }
    }
  }

  private void addExactPrefixItems(DefaultListModel model, Set<LookupElement> firstItems) {
    for (final LookupItemWeightComparable comparable : myItemsMap.keySet()) {
      for (final LookupElement item : myItemsMap.get(comparable)) {
        if (isExactPrefixItem(item)) {
          model.addElement(item);
          firstItems.add(item);
        }
      }
    }
  }

  private boolean prefixMatches(final LookupElement item) {
    if (myAdditionalPrefix.length() == 0) return item.isPrefixMatched();

    return item.getPrefixMatcher().cloneWithPrefix(item.getPrefixMatcher().getPrefix() + myAdditionalPrefix).prefixMatches(item);
  }

  /**
   * @return point in layered pane coordinate system.
   */
  public Point calculatePosition(){
    Dimension dim = getComponent().getPreferredSize();
    int lookupStart = getLookupStart();
    if (lookupStart < 0) {
      LOG.assertTrue(false, lookupStart + "; minprefix=" + myMinPrefixLength + "; offset=" + myEditor.getCaretModel().getOffset() + "; element=" + getPsiElement());
    }

    LogicalPosition pos = myEditor.offsetToLogicalPosition(lookupStart);
    Point location = myEditor.logicalPositionToXY(pos);
    location.y += myEditor.getLineHeight();
    JComponent editorComponent = myEditor.getComponent();
    JComponent internalComponent = myEditor.getContentComponent();
    final JRootPane rootPane = editorComponent.getRootPane();
    if (rootPane == null) {
      synchronized (myItems) {
        LOG.assertTrue(false, myItems);
      }
    }
    JLayeredPane layeredPane = rootPane.getLayeredPane();
    Point layeredPanePoint=SwingUtilities.convertPoint(internalComponent,location, layeredPane);
    layeredPanePoint.x -= myCellRenderer.getIconIndent();
    if (dim.width > layeredPane.getWidth()){
      dim.width = layeredPane.getWidth();
    }
    int wshift = layeredPane.getWidth() - (layeredPanePoint.x + dim.width);
    if (wshift < 0){
      layeredPanePoint.x += wshift;
    }

    int shiftLow = layeredPane.getHeight() - (layeredPanePoint.y + dim.height);
    int shiftHigh = layeredPanePoint.y - dim.height;
    myPositionedAbove = shiftLow < 0 && shiftLow < shiftHigh ? Boolean.TRUE : Boolean.FALSE;

    if (myPositionedAbove.booleanValue()){
      layeredPanePoint.y -= dim.height + myEditor.getLineHeight();
    }
    return layeredPanePoint;
  }

  public void finishLookup(final char completionChar) {
    if (myShownStamp > 0 && System.currentTimeMillis() - myShownStamp < 42 && !ApplicationManager.getApplication().isUnitTestMode()) {
      return;
    }

    final LookupElement item = (LookupElement)myList.getSelectedValue();
    doHide(false);
    if (item == null ||
        item instanceof EmptyLookupItem ||
        item.getObject() instanceof DeferredUserLookupValue &&
        item instanceof LookupItem &&
        !((DeferredUserLookupValue)item.getObject()).handleUserSelection((LookupItem)item, myProject)) {
      fireItemSelected(null, completionChar);
      return;
    }

    ApplicationManager.getApplication().runWriteAction(new Runnable() {<commit_msg>ensure file's writable on completion<commit_after>package com.intellij.codeInsight.lookup.impl;

import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.codeInsight.completion.CompletionPreferencePolicy;
import com.intellij.codeInsight.completion.PrefixMatcher;
import com.intellij.codeInsight.completion.impl.CamelHumpMatcher;
import com.intellij.codeInsight.hint.HintManagerImpl;
import com.intellij.codeInsight.hint.HintUtil;
import com.intellij.codeInsight.lookup.*;
import com.intellij.featureStatistics.FeatureUsageTracker;
import com.intellij.lang.LangBundle;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.event.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.proximity.PsiProximityComparator;
import com.intellij.ui.LightweightHint;
import com.intellij.ui.ListScrollingUtil;
import com.intellij.ui.plaf.beg.BegPopupMenuBorder;
import com.intellij.ui.popup.PopupIcons;
import com.intellij.util.CollectConsumer;
import com.intellij.util.SmartList;
import com.intellij.util.containers.SortedList;
import com.intellij.util.ui.AsyncProcessIcon;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;

import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.List;

public class LookupImpl extends LightweightHint implements Lookup, Disposable {
  private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.lookup.impl.LookupImpl");
  private static final int MAX_PREFERRED_COUNT = 5;

  private static final LookupItem EMPTY_LOOKUP_ITEM = new LookupItem("preselect", "preselect");

  private final Project myProject;
  private final Editor myEditor;
  private final SortedList<LookupElement> myItems;
  private final SortedMap<LookupItemWeightComparable, SortedList<LookupElement>> myItemsMap;
  private final Map<LookupElement, Collection<LookupElementAction>> myItemActions = new THashMap<LookupElement, Collection<LookupElementAction>>();
  private int myMinPrefixLength;
  private int myPreferredItemsCount;
  private int myInitialOffset;
  private long myShownStamp = -1;
  private String myInitialPrefix;
  @Nullable private final LookupItemPreferencePolicy myItemPreferencePolicy;

  private RangeMarker myLookupStartMarker;
  private final JList myList;
  private final LookupCellRenderer myCellRenderer;
  private Boolean myPositionedAbove = null;

  private CaretListener myEditorCaretListener;
  private SelectionListener myEditorSelectionListener;
  private EditorMouseListener myEditorMouseListener;

  private final ArrayList<LookupListener> myListeners = new ArrayList<LookupListener>();

  private boolean myDisposed = false;
  private boolean myHidden = false;
  private LookupElement myPreselectedItem = EMPTY_LOOKUP_ITEM;
  private boolean myDirty;
  private String myAdditionalPrefix = "";
  private final PsiElement myElement;
  private final AsyncProcessIcon myProcessIcon;
  private final Comparator<LookupElement> myComparator;
  private volatile boolean myCalculating;
  private final JLabel myAdComponent;
  private volatile String myAdText;
  private volatile int myLookupWidth = 50;

  public LookupImpl(Project project, Editor editor, LookupElement[] items, @Nullable LookupItemPreferencePolicy itemPreferencePolicy){
    super(new JPanel(new BorderLayout()));
    myProject = project;
    myEditor = editor;
    myItemPreferencePolicy = itemPreferencePolicy;
    myInitialOffset = myEditor.getSelectionModel().hasSelection() ? myEditor.getSelectionModel().getSelectionStart() : myEditor.getCaretModel().getOffset();

    final Document document = myEditor.getDocument();
    final PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(document);
    myElement = psiFile == null ? null : psiFile.findElementAt(myEditor.getCaretModel().getOffset());

    final PsiProximityComparator proximityComparator = new PsiProximityComparator(myElement == null ? psiFile : myElement);
    myComparator = new Comparator<LookupElement>() {
      public int compare(LookupElement o1, LookupElement o2) {
        if (o1 instanceof LookupItem && o2 instanceof LookupItem) {
          double priority1 = ((LookupItem)o1).getPriority();
          double priority2 = ((LookupItem)o2).getPriority();
          if (priority1 > priority2) return -1;
          if (priority2 > priority1) return 1;

          int grouping1 = ((LookupItem)o1).getGrouping();
          int grouping2 = ((LookupItem)o2).getGrouping();
          if (grouping1 > grouping2) return -1;
          if (grouping2 > grouping1) return 1;
        }

        int stringCompare = o1.getLookupString().compareToIgnoreCase(o2.getLookupString());
        if (stringCompare != 0) return stringCompare;

        return proximityComparator.compare(o1.getObject(), o2.getObject());
      }
    };
    myItems = new SortedList<LookupElement>(myComparator);
    myItemsMap = new TreeMap<LookupItemWeightComparable, SortedList<LookupElement>>();

    myProcessIcon = new AsyncProcessIcon("Completion progress");
    myProcessIcon.setVisible(false);
    myList = new JList(new DefaultListModel());
    myCellRenderer = new LookupCellRenderer(this);
    myList.setCellRenderer(myCellRenderer);

    for (final LookupElement item : items) {
      addItem(item);
    }

    myList.setFocusable(false);

    myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    myList.setBackground(LookupCellRenderer.BACKGROUND_COLOR);

    JScrollPane scrollPane = new JScrollPane(myList);
    scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
    getComponent().add(scrollPane, BorderLayout.NORTH);
    scrollPane.setBorder(null);

    JPanel bottomPanel = new JPanel(new BorderLayout());

    bottomPanel.add(myProcessIcon, BorderLayout.EAST);
    myAdComponent = HintUtil.createAdComponent(null);
    bottomPanel.add(myAdComponent, BorderLayout.CENTER);
    getComponent().add(bottomPanel, BorderLayout.SOUTH);
    getComponent().setBorder(new BegPopupMenuBorder());

    updateList();
    selectMostPreferableItem();
  }

  public AsyncProcessIcon getProcessIcon() {
    return myProcessIcon;
  }

  public boolean isCalculating() {
    return myCalculating;
  }

  public void setCalculating(final boolean calculating) {
    myCalculating = calculating;
  }

  public int getPreferredItemsCount() {
    return myPreferredItemsCount;
  }

  public void markDirty() {
    myDirty = true;
    myPreselectedItem = null;
  }

  @TestOnly
  public void resort() {
    final ArrayList<LookupElement> items = new ArrayList<LookupElement>(myItems);
    myDirty = false;
    myPreselectedItem = EMPTY_LOOKUP_ITEM;
    myItemsMap.clear();
    myItems.clear();
    for (final LookupElement item : items) {
      addItem(item);
    }
    updateList();
  }

  public void addItem(LookupElement item) {
    synchronized (myItems) {
      myItems.add(item);
      addItemWeight(item);

      final CollectConsumer<LookupElementAction> consumer = new CollectConsumer<LookupElementAction>();
      for (LookupActionProvider provider : LookupActionProvider.EP_NAME.getExtensions()) {
        provider.fillActions(item, this, consumer);
      }
      myItemActions.put(item, consumer.getResult());
    }
    int maxWidth = myCellRenderer.updateMaximumWidth(item);
    myLookupWidth = Math.max(maxWidth, myLookupWidth);
  }

  public Collection<LookupElementAction> getActionsFor(LookupElement element) {
    synchronized (myItems) {
      final Collection<LookupElementAction> collection = myItemActions.get(element);
      return collection == null ? Collections.<LookupElementAction>emptyList() : collection;
    }
  }

  private void addItemWeight(final LookupElement item) {
    final Comparable[] weight = getWeight(myItemPreferencePolicy, myElement, item);
    final LookupItemWeightComparable key = new LookupItemWeightComparable(item instanceof LookupItem ? ((LookupItem)item).getPriority() : 0, weight);
    SortedList<LookupElement> list = myItemsMap.get(key);
    if (list == null) myItemsMap.put(key, list = new SortedList<LookupElement>(myComparator));
    list.add(item);
  }

  @Nullable
  public LookupItemPreferencePolicy getItemPreferencePolicy() {
    return myItemPreferencePolicy;
  }

  private static Comparable[] getWeight(final LookupItemPreferencePolicy itemPreferencePolicy, final PsiElement context,
                                        final LookupElement item) {
    if (itemPreferencePolicy instanceof CompletionPreferencePolicy) {
      return ((CompletionPreferencePolicy)itemPreferencePolicy).getWeight(item);
    }
    Comparable i = null;
    if (item.getObject() instanceof PsiElement) {
      i = PsiProximityComparator.getProximity((PsiElement)item.getObject(), context);
    }
    return new Comparable[]{i};
  }

  public int getMinPrefixLength() {
    return myMinPrefixLength;
  }

  public JList getList() {
    return myList;
  }

  public LookupElement[] getItems(){
    synchronized (myItems) {
      return myItems.toArray(new LookupElement[myItems.size()]);
    }
  }

  public void setAdvertisementText(@Nullable String text) {
    myAdText = text;
  }

  public String getAdvertisementText() {
    return myAdText;
  }


  public String getAdditionalPrefix() {
    return myAdditionalPrefix;
  }

  public void setAdditionalPrefix(final String additionalPrefix) {
    myAdditionalPrefix = additionalPrefix;
    myInitialPrefix = null;
    markDirty();
    updateList();
  }

  public final void updateList() {
    synchronized (myItems) {
      int minPrefixLength = myItems.isEmpty() ? 0 : Integer.MAX_VALUE;
      for (final LookupElement item : myItems) {
        minPrefixLength = Math.min(item.getPrefixMatcher().getPrefix().length(), minPrefixLength);
      }
      if (myMinPrefixLength != minPrefixLength) {
        myLookupStartMarker = null;
      }
      myMinPrefixLength = minPrefixLength;

      Object oldSelected = !myDirty ? null : myList.getSelectedValue();
      DefaultListModel model = (DefaultListModel)myList.getModel();
      model.clear();

      Set<LookupElement> firstItems = new THashSet<LookupElement>();

      addExactPrefixItems(model, firstItems);

      boolean hasExactPrefixes = !firstItems.isEmpty();

      addMostRelevantItems(model, firstItems);

      final boolean hasPreselectedItem = addPreselectedItem(model, firstItems);

      myPreferredItemsCount = firstItems.size();

      addRemainingItemsLexicographically(model, firstItems);

      boolean isEmpty = model.getSize() == 0;
      if (isEmpty) {
        addEmptyItem(model);
      } else {
        myList.setFixedCellWidth(myLookupWidth);
      }
      myList.setFixedCellHeight(myCellRenderer.getListCellRendererComponent(myList, myList.getModel().getElementAt(0), 0, false, false).getPreferredSize().height);

      myList.setVisibleRowCount(Math.min(myList.getModel().getSize(), CodeInsightSettings.getInstance().LOOKUP_HEIGHT));

      myAdComponent.setText(myAdText);

      if (!isEmpty) {
        if (oldSelected != null) {
          if (hasExactPrefixes || !ListScrollingUtil.selectItem(myList, oldSelected)) {
            selectMostPreferableItem();
          }
        }
        else {
          if (myPreselectedItem == EMPTY_LOOKUP_ITEM) {
            selectMostPreferableItem();
            myPreselectedItem = getCurrentItem();
          }
          else if (hasPreselectedItem && !hasExactPrefixes) {
            ListScrollingUtil.selectItem(myList, myPreselectedItem);
          }
          else {
            selectMostPreferableItem();
          }
        }
      }
    }
  }

  private void addEmptyItem(DefaultListModel model) {
    LookupItem<String> item = new EmptyLookupItem(myCalculating ? " " : LangBundle.message("completion.no.suggestions"));
    item.setPrefixMatcher(new CamelHumpMatcher(""));
    if (!myCalculating) {
      final int maxWidth = myCellRenderer.updateMaximumWidth(item);
      myList.setFixedCellWidth(Math.max(maxWidth, myLookupWidth));
    }

    model.addElement(item);
  }

  private void addRemainingItemsLexicographically(DefaultListModel model, Set<LookupElement> firstItems) {
    for (LookupElement item : myItems) {
      if (!firstItems.contains(item) && prefixMatches(item)) {
        model.addElement(item);
      }
    }
  }

  private boolean addPreselectedItem(DefaultListModel model, Set<LookupElement> firstItems) {
    final boolean hasPreselectedItem = !myDirty && myPreselectedItem != EMPTY_LOOKUP_ITEM;
    if (hasPreselectedItem && !firstItems.contains(myPreselectedItem)) {
      firstItems.add(myPreselectedItem);
      model.addElement(myPreselectedItem);
    }
    return hasPreselectedItem;
  }

  private void addMostRelevantItems(DefaultListModel model, Set<LookupElement> firstItems) {
    for (final LookupItemWeightComparable comparable : myItemsMap.keySet()) {
      final List<LookupElement> suitable = new SmartList<LookupElement>();
      for (final LookupElement item : myItemsMap.get(comparable)) {
        if (!firstItems.contains(item) && prefixMatches(item)) {
          suitable.add(item);
        }
      }

      if (firstItems.size() + suitable.size() > MAX_PREFERRED_COUNT) break;
      for (final LookupElement item : suitable) {
        firstItems.add(item);
        model.addElement(item);
      }
    }
  }

  private void addExactPrefixItems(DefaultListModel model, Set<LookupElement> firstItems) {
    for (final LookupItemWeightComparable comparable : myItemsMap.keySet()) {
      for (final LookupElement item : myItemsMap.get(comparable)) {
        if (isExactPrefixItem(item)) {
          model.addElement(item);
          firstItems.add(item);
        }
      }
    }
  }

  private boolean prefixMatches(final LookupElement item) {
    if (myAdditionalPrefix.length() == 0) return item.isPrefixMatched();

    return item.getPrefixMatcher().cloneWithPrefix(item.getPrefixMatcher().getPrefix() + myAdditionalPrefix).prefixMatches(item);
  }

  /**
   * @return point in layered pane coordinate system.
   */
  public Point calculatePosition(){
    Dimension dim = getComponent().getPreferredSize();
    int lookupStart = getLookupStart();
    if (lookupStart < 0) {
      LOG.assertTrue(false, lookupStart + "; minprefix=" + myMinPrefixLength + "; offset=" + myEditor.getCaretModel().getOffset() + "; element=" + getPsiElement());
    }

    LogicalPosition pos = myEditor.offsetToLogicalPosition(lookupStart);
    Point location = myEditor.logicalPositionToXY(pos);
    location.y += myEditor.getLineHeight();
    JComponent editorComponent = myEditor.getComponent();
    JComponent internalComponent = myEditor.getContentComponent();
    final JRootPane rootPane = editorComponent.getRootPane();
    if (rootPane == null) {
      synchronized (myItems) {
        LOG.assertTrue(false, myItems);
      }
    }
    JLayeredPane layeredPane = rootPane.getLayeredPane();
    Point layeredPanePoint=SwingUtilities.convertPoint(internalComponent,location, layeredPane);
    layeredPanePoint.x -= myCellRenderer.getIconIndent();
    if (dim.width > layeredPane.getWidth()){
      dim.width = layeredPane.getWidth();
    }
    int wshift = layeredPane.getWidth() - (layeredPanePoint.x + dim.width);
    if (wshift < 0){
      layeredPanePoint.x += wshift;
    }

    int shiftLow = layeredPane.getHeight() - (layeredPanePoint.y + dim.height);
    int shiftHigh = layeredPanePoint.y - dim.height;
    myPositionedAbove = shiftLow < 0 && shiftLow < shiftHigh ? Boolean.TRUE : Boolean.FALSE;

    if (myPositionedAbove.booleanValue()){
      layeredPanePoint.y -= dim.height + myEditor.getLineHeight();
    }
    return layeredPanePoint;
  }

  public void finishLookup(final char completionChar) {
    if (myShownStamp > 0 && System.currentTimeMillis() - myShownStamp < 42 && !ApplicationManager.getApplication().isUnitTestMode()) {
      return;
    }

    final LookupElement item = (LookupElement)myList.getSelectedValue();
    doHide(false);
    if (item == null ||
        item instanceof EmptyLookupItem ||
        item.getObject() instanceof DeferredUserLookupValue &&
        item instanceof LookupItem &&
        !((DeferredUserLookupValue)item.getObject()).handleUserSelection((LookupItem)item, myProject)) {
      fireItemSelected(null, completionChar);
      return;
    }

    final PsiFile file = getPsiFile();
    if (file != null && !WriteCommandAction.ensureFilesWritable(myProject, Arrays.asList(file))) {
      return;
    }

    ApplicationManager.getApplication().runWriteAction(new Runnable() {
{'fraction_non_alphanumeric': 0.09863588667366212, 'fraction_numerical': 0.0026232948583420775, 'mean_word_length': 4.207011686143573, 'pattern_counts': {'":': 0, '<': 77, '<?xml version=': 0, '>': 81, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6743827', 'n_tokens_mistral': 9970, 'n_tokens_neox': 9713, 'n_words': 1938}