Changeset View
Changeset View
Standalone View
Standalone View
src/completion/katecompletionmodel.cpp
Show First 20 Lines • Show All 364 Lines • ▼ Show 20 Line(s) | 364 | { | |||
---|---|---|---|---|---|
365 | return static_cast<KateCompletionWidget *>(QObject::parent()); | 365 | return static_cast<KateCompletionWidget *>(QObject::parent()); | ||
366 | } | 366 | } | ||
367 | 367 | | |||
368 | KTextEditor::ViewPrivate *KateCompletionModel::view() const | 368 | KTextEditor::ViewPrivate *KateCompletionModel::view() const | ||
369 | { | 369 | { | ||
370 | return widget()->view(); | 370 | return widget()->view(); | ||
371 | } | 371 | } | ||
372 | 372 | | |||
373 | void KateCompletionModel::setMatchCaseSensitivity(Qt::CaseSensitivity cs) | 373 | void KateCompletionModel::setMatchCaseSensitivity(Qt::CaseSensitivity match_cs) | ||
374 | { | 374 | { | ||
375 | m_matchCaseSensitivity = cs; | 375 | m_matchCaseSensitivity = match_cs; | ||
376 | Q_ASSERT(m_exactMatchCaseSensitivity == m_matchCaseSensitivity || m_matchCaseSensitivity == Qt::CaseInsensitive); | ||||
377 | } | ||||
378 | | ||||
379 | void KateCompletionModel::setMatchCaseSensitivity(Qt::CaseSensitivity match_cs, Qt::CaseSensitivity exact_match_cs) | ||||
380 | { | ||||
381 | m_matchCaseSensitivity = match_cs; | ||||
382 | m_exactMatchCaseSensitivity = exact_match_cs; | ||||
383 | Q_ASSERT(m_exactMatchCaseSensitivity == m_matchCaseSensitivity || m_matchCaseSensitivity == Qt::CaseInsensitive); | ||||
376 | } | 384 | } | ||
377 | 385 | | |||
378 | int KateCompletionModel::columnCount(const QModelIndex &) const | 386 | int KateCompletionModel::columnCount(const QModelIndex &) const | ||
379 | { | 387 | { | ||
380 | return isColumnMergingEnabled() && !m_columnMerges.isEmpty() ? m_columnMerges.count() : KTextEditor::CodeCompletionModel::ColumnCount; | 388 | return isColumnMergingEnabled() && !m_columnMerges.isEmpty() ? m_columnMerges.count() : KTextEditor::CodeCompletionModel::ColumnCount; | ||
381 | } | 389 | } | ||
382 | 390 | | |||
383 | KateCompletionModel::ModelRow KateCompletionModel::modelRowPair(const QModelIndex &index) const | 391 | KateCompletionModel::ModelRow KateCompletionModel::modelRowPair(const QModelIndex &index) const | ||
▲ Show 20 Lines • Show All 1154 Lines • ▼ Show 20 Line(s) | 1545 | if (doInitialMatch) { | |||
1538 | filter(); | 1546 | filter(); | ||
1539 | match(); | 1547 | match(); | ||
1540 | } | 1548 | } | ||
1541 | } | 1549 | } | ||
1542 | 1550 | | |||
1543 | bool KateCompletionModel::Item::operator <(const Item &rhs) const | 1551 | bool KateCompletionModel::Item::operator <(const Item &rhs) const | ||
1544 | { | 1552 | { | ||
1545 | int ret = 0; | 1553 | int ret = 0; | ||
1546 | 1554 | | |||
mwolff: unrelated whitespace changes should be fixed in separate commits | |||||
1547 | //qCDebug(LOG_KTE) << c1 << " c/w " << c2 << " -> " << (model->isSortingReverse() ? ret > 0 : ret < 0) << " (" << ret << ")"; | 1555 | //qCDebug(LOG_KTE) << c1 << " c/w " << c2 << " -> " << (model->isSortingReverse() ? ret > 0 : ret < 0) << " (" << ret << ")"; | ||
1548 | 1556 | | |||
1549 | if(m_unimportant && !rhs.m_unimportant){ | 1557 | if(m_unimportant && !rhs.m_unimportant){ | ||
1550 | return false; | 1558 | return false; | ||
1551 | } | 1559 | } | ||
1552 | 1560 | | |||
1553 | if(!m_unimportant && rhs.m_unimportant){ | 1561 | if(!m_unimportant && rhs.m_unimportant){ | ||
1554 | return true; | 1562 | return true; | ||
1555 | } | 1563 | } | ||
1556 | 1564 | | |||
1557 | if (matchCompletion < rhs.matchCompletion) { | 1565 | if (matchCompletion < rhs.matchCompletion) { | ||
1558 | // enums are ordered in the order items should be displayed | 1566 | // enums are ordered in the order items should be displayed | ||
1559 | return true; | 1567 | return true; | ||
1560 | } | 1568 | } | ||
1561 | if (matchCompletion > rhs.matchCompletion) { | 1569 | if (matchCompletion > rhs.matchCompletion) { | ||
1562 | return false; | 1570 | return false; | ||
1563 | } | 1571 | } | ||
1564 | 1572 | | |||
1573 | if (ret == 0) { | ||||
1574 | const QString& filter = rhs.model->currentCompletion(rhs.m_sourceRow.first); | ||||
1575 | bool thisStartWithFilter = m_nameColumn.startsWith(filter, Qt::CaseSensitive); | ||||
mwolff: here and below:remove space after `!` | |||||
1576 | bool rhsStartsWithFilter = rhs.m_nameColumn.startsWith(filter, Qt::CaseSensitive); | ||||
1577 | | ||||
1578 | if( thisStartWithFilter && !rhsStartsWithFilter ) { | ||||
1579 | return true; | ||||
1580 | } | ||||
1581 | if( rhsStartsWithFilter && !thisStartWithFilter ) { | ||||
1582 | return false; | ||||
1583 | } | ||||
1584 | } | ||||
1585 | | ||||
1565 | if (model->isSortingByInheritanceDepth()) { | 1586 | if (model->isSortingByInheritanceDepth()) { | ||
1566 | ret = inheritanceDepth - rhs.inheritanceDepth; | 1587 | ret = inheritanceDepth - rhs.inheritanceDepth; | ||
1567 | } | 1588 | } | ||
1568 | 1589 | | |||
1569 | if (ret == 0 && model->isSortingAlphabetical()) { | 1590 | if (ret == 0 && model->isSortingAlphabetical()) { | ||
1570 | // Do not use localeAwareCompare, because it is simply too slow for a list of about 1000 items | 1591 | // Do not use localeAwareCompare, because it is simply too slow for a list of about 1000 items | ||
1571 | ret = QString::compare(m_nameColumn, rhs.m_nameColumn, model->sortingCaseSensitivity()); | 1592 | ret = QString::compare(m_nameColumn, rhs.m_nameColumn, model->sortingCaseSensitivity()); | ||
1572 | } | 1593 | } | ||
1573 | 1594 | | |||
1574 | if (ret == 0) { | 1595 | if (ret == 0) { | ||
1575 | const QString& filter = rhs.model->currentCompletion(rhs.m_sourceRow.first); | | |||
1576 | if( m_nameColumn.startsWith(filter, Qt::CaseSensitive) ) { | | |||
1577 | return true; | | |||
1578 | } | | |||
1579 | if( rhs.m_nameColumn.startsWith(filter, Qt::CaseSensitive) ) { | | |||
1580 | return false; | | |||
1581 | } | | |||
1582 | | ||||
1583 | // FIXME need to define a better default ordering for multiple model display | 1596 | // FIXME need to define a better default ordering for multiple model display | ||
1584 | ret = m_sourceRow.second.row() - rhs.m_sourceRow.second.row(); | 1597 | ret = m_sourceRow.second.row() - rhs.m_sourceRow.second.row(); | ||
1585 | } | 1598 | } | ||
1586 | 1599 | | |||
1587 | return ret < 0; | 1600 | return ret < 0; | ||
1588 | } | 1601 | } | ||
1589 | 1602 | | |||
1590 | void KateCompletionModel::Group::addItem(Item i, bool notifyModel) | 1603 | void KateCompletionModel::Group::addItem(Item i, bool notifyModel) | ||
▲ Show 20 Lines • Show All 276 Lines • ▼ Show 20 Line(s) | 1879 | foreach (Group *group, m_rowTable) | |||
1867 | foreach (const Item &item, group->filtered) | 1880 | foreach (const Item &item, group->filtered) | ||
1868 | if (item.sourceRow().first != hideModel) { | 1881 | if (item.sourceRow().first != hideModel) { | ||
1869 | return false; | 1882 | return false; | ||
1870 | } | 1883 | } | ||
1871 | } | 1884 | } | ||
1872 | 1885 | | |||
1873 | return doHide; | 1886 | return doHide; | ||
1874 | } | 1887 | } | ||
1875 | 1888 | | |||
mwolff: static or anon namespace | |||||
1889 | static inline QChar toLowerIfInsensitive(QChar c, Qt::CaseSensitivity caseSensitive) | ||||
1890 | { | ||||
1891 | return (caseSensitive == Qt::CaseInsensitive) ? c.toLower() : c; | ||||
1892 | } | ||||
1893 | | ||||
1876 | static inline bool matchesAbbreviationHelper(const QString &word, const QString &typed, const QVarLengthArray<int, 32> &offsets, | 1894 | static inline bool matchesAbbreviationHelper(const QString &word, const QString &typed, const QVarLengthArray<int, 32> &offsets, | ||
1877 | int &depth, int atWord = -1, int i = 0) | 1895 | Qt::CaseSensitivity caseSensitive, int &depth, int atWord = -1, int i = 0) | ||
1878 | { | 1896 | { | ||
1879 | int atLetter = 1; | 1897 | int atLetter = 1; | ||
1880 | for (; i < typed.size(); i++) { | 1898 | for (; i < typed.size(); i++) { | ||
1881 | const QChar c = typed.at(i).toLower(); | 1899 | const QChar c = toLowerIfInsensitive(typed.at(i), caseSensitive); | ||
1882 | bool haveNextWord = offsets.size() > atWord + 1; | 1900 | bool haveNextWord = offsets.size() > atWord + 1; | ||
1883 | bool canCompare = atWord != -1 && word.size() > offsets.at(atWord) + atLetter; | 1901 | bool canCompare = atWord != -1 && word.size() > offsets.at(atWord) + atLetter; | ||
1884 | if (canCompare && c == word.at(offsets.at(atWord) + atLetter).toLower()) { | 1902 | if (canCompare && c == toLowerIfInsensitive(word.at(offsets.at(atWord) + atLetter), caseSensitive)) { | ||
1885 | // the typed letter matches a letter after the current word beginning | 1903 | // the typed letter matches a letter after the current word beginning | ||
1886 | if (! haveNextWord || c != word.at(offsets.at(atWord + 1)).toLower()) { | 1904 | if (! haveNextWord || c != toLowerIfInsensitive(word.at(offsets.at(atWord + 1)), caseSensitive)) { | ||
1887 | // good, simple case, no conflict | 1905 | // good, simple case, no conflict | ||
1888 | atLetter += 1; | 1906 | atLetter += 1; | ||
1889 | continue; | 1907 | continue; | ||
1890 | } | 1908 | } | ||
1891 | // For maliciously crafted data, the code used here theoretically can have very high | 1909 | // For maliciously crafted data, the code used here theoretically can have very high | ||
1892 | // complexity. Thus ensure we don't run into this case, by limiting the amount of branches | 1910 | // complexity. Thus ensure we don't run into this case, by limiting the amount of branches | ||
1893 | // we walk through to 128. | 1911 | // we walk through to 128. | ||
1894 | depth++; | 1912 | depth++; | ||
1895 | if (depth > 128) { | 1913 | if (depth > 128) { | ||
1896 | return false; | 1914 | return false; | ||
1897 | } | 1915 | } | ||
1898 | // the letter matches both the next word beginning and the next character in the word | 1916 | // the letter matches both the next word beginning and the next character in the word | ||
1899 | if (haveNextWord && matchesAbbreviationHelper(word, typed, offsets, depth, atWord + 1, i + 1)) { | 1917 | if (haveNextWord && matchesAbbreviationHelper(word, typed, offsets, caseSensitive, depth, atWord + 1, i + 1)) { | ||
1900 | // resolving the conflict by taking the next word's first character worked, fine | 1918 | // resolving the conflict by taking the next word's first character worked, fine | ||
1901 | return true; | 1919 | return true; | ||
1902 | } | 1920 | } | ||
1903 | // otherwise, continue by taking the next letter in the current word. | 1921 | // otherwise, continue by taking the next letter in the current word. | ||
1904 | atLetter += 1; | 1922 | atLetter += 1; | ||
1905 | continue; | 1923 | continue; | ||
1906 | } else if (haveNextWord && c == word.at(offsets.at(atWord + 1)).toLower()) { | 1924 | } else if (haveNextWord && c == toLowerIfInsensitive(word.at(offsets.at(atWord + 1)), caseSensitive)) { | ||
1907 | // the typed letter matches the next word beginning | 1925 | // the typed letter matches the next word beginning | ||
1908 | atWord++; | 1926 | atWord++; | ||
1909 | atLetter = 1; | 1927 | atLetter = 1; | ||
1910 | continue; | 1928 | continue; | ||
1911 | } | 1929 | } | ||
1912 | // no match | 1930 | // no match | ||
1913 | return false; | 1931 | return false; | ||
1914 | } | 1932 | } | ||
1915 | // all characters of the typed word were matched | 1933 | // all characters of the typed word were matched | ||
1916 | return true; | 1934 | return true; | ||
1917 | } | 1935 | } | ||
1918 | 1936 | | |||
1919 | bool KateCompletionModel::matchesAbbreviation(const QString &word, const QString &typed) | 1937 | bool KateCompletionModel::matchesAbbreviation(const QString &word, const QString &typed, Qt::CaseSensitivity caseSensitive) | ||
1920 | { | 1938 | { | ||
1921 | // A mismatch is very likely for random even for the first letter, | 1939 | // A mismatch is very likely for random even for the first letter, | ||
1922 | // thus this optimization makes sense. | 1940 | // thus this optimization makes sense. | ||
1923 | if (word.at(0).toLower() != typed.at(0).toLower()) { | 1941 | if (toLowerIfInsensitive(word.at(0), caseSensitive) != toLowerIfInsensitive(typed.at(0), caseSensitive)) { | ||
1924 | return false; | 1942 | return false; | ||
1925 | } | 1943 | } | ||
1926 | 1944 | | |||
1927 | // First, check if all letters are contained in the word in the right order. | 1945 | // First, check if all letters are contained in the word in the right order. | ||
1928 | int atLetter = 0; | 1946 | int atLetter = 0; | ||
1929 | foreach (const QChar c, typed) { | 1947 | foreach (const QChar c, typed) { | ||
1930 | while (c.toLower() != word.at(atLetter).toLower()) { | 1948 | while (toLowerIfInsensitive(c, caseSensitive) != toLowerIfInsensitive(word.at(atLetter), caseSensitive)) { | ||
1931 | atLetter += 1; | 1949 | atLetter += 1; | ||
1932 | if (atLetter >= word.size()) { | 1950 | if (atLetter >= word.size()) { | ||
1933 | return false; | 1951 | return false; | ||
1934 | } | 1952 | } | ||
1935 | } | 1953 | } | ||
1936 | } | 1954 | } | ||
1937 | 1955 | | |||
1938 | bool haveUnderscore = true; | 1956 | bool haveUnderscore = true; | ||
Show All 9 Lines | 1964 | for (int i = 0; i < word.size(); i++) { | |||
1948 | if (c == QLatin1Char('_')) { | 1966 | if (c == QLatin1Char('_')) { | ||
1949 | haveUnderscore = true; | 1967 | haveUnderscore = true; | ||
1950 | } else if (haveUnderscore || c.isUpper()) { | 1968 | } else if (haveUnderscore || c.isUpper()) { | ||
1951 | offsets.append(i); | 1969 | offsets.append(i); | ||
1952 | haveUnderscore = false; | 1970 | haveUnderscore = false; | ||
1953 | } | 1971 | } | ||
1954 | } | 1972 | } | ||
1955 | int depth = 0; | 1973 | int depth = 0; | ||
1956 | return matchesAbbreviationHelper(word, typed, offsets, depth); | 1974 | return matchesAbbreviationHelper(word, typed, offsets, caseSensitive, depth); | ||
1957 | } | 1975 | } | ||
1958 | 1976 | | |||
1959 | static inline bool containsAtWordBeginning(const QString &word, const QString &typed, Qt::CaseSensitivity caseSensitive) | 1977 | static inline bool containsAtWordBeginning(const QString &word, const QString &typed, Qt::CaseSensitivity caseSensitive) | ||
1960 | { | 1978 | { | ||
1961 | for (int i = 1; i < word.size(); i++) { | 1979 | for (int i = 1; i < word.size(); i++) { | ||
1962 | // The current position is a word beginning if the previous character was an underscore | 1980 | // The current position is a word beginning if the previous character was an underscore | ||
1963 | // or if the current character is uppercase. Subsequent uppercase characters do not count, | 1981 | // or if the current character is uppercase. Subsequent uppercase characters do not count, | ||
1964 | // to handle the special case of UPPER_CASE_VARS properly. | 1982 | // to handle the special case of UPPER_CASE_VARS properly. | ||
Show All 31 Lines | 2010 | if (matchCompletion == NoMatch) { | |||
1996 | // Starting at 1 saves looking at the beginning of the word, that was already checked above. | 2014 | // Starting at 1 saves looking at the beginning of the word, that was already checked above. | ||
1997 | if (containsAtWordBeginning(m_nameColumn, match, model->matchCaseSensitivity())) { | 2015 | if (containsAtWordBeginning(m_nameColumn, match, model->matchCaseSensitivity())) { | ||
1998 | matchCompletion = ContainsMatch; | 2016 | matchCompletion = ContainsMatch; | ||
1999 | } | 2017 | } | ||
2000 | } | 2018 | } | ||
2001 | 2019 | | |||
2002 | if (matchCompletion == NoMatch && !m_nameColumn.isEmpty() && !match.isEmpty()) { | 2020 | if (matchCompletion == NoMatch && !m_nameColumn.isEmpty() && !match.isEmpty()) { | ||
2003 | // if still no match, try abbreviation matching | 2021 | // if still no match, try abbreviation matching | ||
2004 | if (matchesAbbreviation(m_nameColumn, match)) { | 2022 | if (matchesAbbreviation(m_nameColumn, match, model->matchCaseSensitivity())) { | ||
2005 | matchCompletion = AbbreviationMatch; | 2023 | matchCompletion = AbbreviationMatch; | ||
2006 | } | 2024 | } | ||
2007 | } | 2025 | } | ||
2008 | 2026 | | |||
maybe simplify this to: if (matchCompletion && m_nameColumn.startsWith(match, model->exactMatchCaseSensitivity())) { matchCompletion = PerfectMatch; m_haveExactMatch = true; } mwolff: maybe simplify this to:
if (matchCompletion && m_nameColumn.startsWith(match, model… | |||||
Hmm, I guess the current version might be a tiny bit faster in general since it only does another startsWith() check if this would be a stricter check than the one done before ... thomassc: Hmm, I guess the current version might be a tiny bit faster in general since it only does… | |||||
2009 | if (matchCompletion && match.length() == m_nameColumn.length()) { | 2027 | if (matchCompletion && match.length() == m_nameColumn.length()) { | ||
2028 | if (model->matchCaseSensitivity() == Qt::CaseInsensitive && | ||||
2029 | model->exactMatchCaseSensitivity() == Qt::CaseSensitive && | ||||
mwolff: remove whitespace after ! | |||||
2030 | !m_nameColumn.startsWith(match, Qt::CaseSensitive)) { | ||||
2031 | return matchCompletion; | ||||
2032 | } | ||||
2010 | matchCompletion = PerfectMatch; | 2033 | matchCompletion = PerfectMatch; | ||
2011 | m_haveExactMatch = true; | 2034 | m_haveExactMatch = true; | ||
2012 | } | 2035 | } | ||
2013 | 2036 | | |||
2014 | return matchCompletion; | 2037 | return matchCompletion; | ||
2015 | } | 2038 | } | ||
2016 | 2039 | | |||
2017 | QString KateCompletionModel::propertyName(KTextEditor::CodeCompletionModel::CompletionProperty property) | 2040 | QString KateCompletionModel::propertyName(KTextEditor::CodeCompletionModel::CompletionProperty property) | ||
▲ Show 20 Lines • Show All 95 Lines • ▼ Show 20 Line(s) | 2135 | { | |||
2113 | return m_currentMatch.value(model); | 2136 | return m_currentMatch.value(model); | ||
2114 | } | 2137 | } | ||
2115 | 2138 | | |||
2116 | Qt::CaseSensitivity KateCompletionModel::matchCaseSensitivity() const | 2139 | Qt::CaseSensitivity KateCompletionModel::matchCaseSensitivity() const | ||
2117 | { | 2140 | { | ||
2118 | return m_matchCaseSensitivity; | 2141 | return m_matchCaseSensitivity; | ||
2119 | } | 2142 | } | ||
2120 | 2143 | | |||
2144 | Qt::CaseSensitivity KateCompletionModel::exactMatchCaseSensitivity() const | ||||
2145 | { | ||||
2146 | return m_exactMatchCaseSensitivity; | ||||
2147 | } | ||||
2148 | | ||||
2121 | void KateCompletionModel::addCompletionModel(KTextEditor::CodeCompletionModel *model) | 2149 | void KateCompletionModel::addCompletionModel(KTextEditor::CodeCompletionModel *model) | ||
2122 | { | 2150 | { | ||
2123 | if (m_completionModels.contains(model)) { | 2151 | if (m_completionModels.contains(model)) { | ||
2124 | return; | 2152 | return; | ||
2125 | } | 2153 | } | ||
2126 | 2154 | | |||
2127 | m_completionModels.append(model); | 2155 | m_completionModels.append(model); | ||
2128 | 2156 | | |||
▲ Show 20 Lines • Show All 276 Lines • Show Last 20 Lines |
unrelated whitespace changes should be fixed in separate commits