Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Utilities
Kate
Commits
2fd9db73
Commit
2fd9db73
authored
Feb 20, 2022
by
Waqar Ahmed
Committed by
Christoph Cullmann
Feb 19, 2022
Browse files
urlbar: Show symbols from LSP for active view
parent
0fce7d47
Pipeline
#139940
passed with stage
in 3 minutes and 25 seconds
Changes
5
Pipelines
3
Hide whitespace changes
Inline
Side-by-side
addons/lspclient/lspclientpluginview.cpp
View file @
2fd9db73
...
...
@@ -451,7 +451,7 @@ class LSPClientActionView : public QObject
QScopedPointer
<
LSPClientCompletion
>
m_completion
;
QScopedPointer
<
LSPClientHover
>
m_hover
;
QScopedPointer
<
KTextEditor
::
TextHintProvider
>
m_forwardHover
;
QScopedPointer
<
QObject
>
m_symbolView
;
QScopedPointer
<
LSPClientSymbolView
>
m_symbolView
;
QPointer
<
QAction
>
m_findDef
;
QPointer
<
QAction
>
m_findDecl
;
...
...
@@ -2917,6 +2917,11 @@ public:
m_hoverViews
.
remove
(
view
);
}
}
QAbstractItemModel
*
documentSymbolsModel
()
{
return
m_symbolView
->
documentSymbolsModel
();
}
};
class
LSPClientPluginViewImpl
:
public
QObject
,
public
KXMLGUIClient
,
public
KTextEditor
::
SessionConfigInterface
...
...
@@ -2969,6 +2974,11 @@ public:
m_actionView
->
sessionDiagnosticSuppressions
().
writeSessionConfig
(
config
);
}
Q_INVOKABLE
QAbstractItemModel
*
documentSymbolsModel
()
{
return
m_actionView
->
documentSymbolsModel
();
}
Q_SIGNALS:
/**
* Signal for outgoing message, the host application will handle them!
...
...
addons/lspclient/lspclientsymbolview.cpp
View file @
2fd9db73
...
...
@@ -17,6 +17,7 @@
#include
<QHBoxLayout>
#include
<QHeaderView>
#include
<QIdentityProxyModel>
#include
<QMenu>
#include
<QPointer>
#include
<QStandardItemModel>
...
...
@@ -28,6 +29,9 @@
#include
<kfts_fuzzy_match.h>
// TODO: Make this globally available in shared/
enum
SymbolViewRoles
{
SymbolRange
=
Qt
::
UserRole
,
ScoreRole
,
IsPlaceholder
};
class
LSPClientViewTrackerImpl
:
public
LSPClientViewTracker
{
Q_OBJECT
...
...
@@ -133,8 +137,8 @@ protected:
return
QSortFilterProxyModel
::
lessThan
(
sourceLeft
,
sourceRight
);
}
const
int
l
=
sourceLeft
.
data
(
Weight
Role
).
toInt
();
const
int
r
=
sourceRight
.
data
(
Weight
Role
).
toInt
();
const
int
l
=
sourceLeft
.
data
(
SymbolViewRoles
::
Score
Role
).
toInt
();
const
int
r
=
sourceRight
.
data
(
SymbolViewRoles
::
Score
Role
).
toInt
();
return
l
<
r
;
}
...
...
@@ -148,13 +152,24 @@ protected:
const
auto
idx
=
sourceModel
()
->
index
(
sourceRow
,
0
,
sourceParent
);
const
QString
symbol
=
idx
.
data
().
toString
();
const
bool
res
=
kfts
::
fuzzy_match
(
m_pattern
,
symbol
,
score
);
sourceModel
()
->
setData
(
idx
,
score
,
Weight
Role
);
sourceModel
()
->
setData
(
idx
,
score
,
SymbolViewRoles
::
Score
Role
);
return
res
;
}
private:
QString
m_pattern
;
static
constexpr
int
WeightRole
=
Qt
::
UserRole
+
1
;
};
class
SymbolViewProxyModel
:
public
QIdentityProxyModel
{
Q_OBJECT
public:
using
QIdentityProxyModel
::
QIdentityProxyModel
;
int
columnCount
(
const
QModelIndex
&
)
const
override
{
return
1
;
}
};
/*
...
...
@@ -201,6 +216,8 @@ class LSPClientSymbolViewImpl : public QObject, public LSPClientSymbolView
// filter model, setup once
LSPClientSymbolViewFilterProxyModel
m_filterModel
;
SymbolViewProxyModel
*
m_identityModel
;
// cached icons for model
const
QIcon
m_icon_pkg
=
QIcon
::
fromTheme
(
QStringLiteral
(
"code-block"
));
const
QIcon
m_icon_class
=
QIcon
::
fromTheme
(
QStringLiteral
(
"code-class"
));
...
...
@@ -214,6 +231,7 @@ public:
,
m_mainWindow
(
mainWin
)
,
m_serverManager
(
std
::
move
(
manager
))
,
m_outline
(
new
QStandardItemModel
())
,
m_identityModel
(
new
SymbolViewProxyModel
(
this
))
{
m_toolview
.
reset
(
m_mainWindow
->
createToolView
(
plugin
,
QStringLiteral
(
"lspclient_symbol_outline"
),
...
...
@@ -249,6 +267,8 @@ public:
m_symbols
->
setModel
(
&
m_filterModel
);
delete
m
;
m_identityModel
->
setSourceModel
(
m_outline
.
get
());
connect
(
m_symbols
,
&
QTreeView
::
customContextMenuRequested
,
this
,
&
self_type
::
showContextMenu
);
connect
(
m_symbols
,
&
QTreeView
::
activated
,
this
,
&
self_type
::
goToSymbol
);
connect
(
m_symbols
,
&
QTreeView
::
clicked
,
this
,
&
self_type
::
goToSymbol
);
...
...
@@ -375,7 +395,7 @@ public:
auto
detail
=
show_detail
&&
!
symbol
.
detail
.
isEmpty
()
?
QStringLiteral
(
" [%1]"
).
arg
(
symbol
.
detail
)
:
QString
();
node
->
setText
(
symbol
.
name
+
detail
);
node
->
setIcon
(
*
icon
);
node
->
setData
(
QVariant
::
fromValue
<
KTextEditor
::
Range
>
(
symbol
.
range
),
Qt
::
UserRol
e
);
node
->
setData
(
QVariant
::
fromValue
<
KTextEditor
::
Range
>
(
symbol
.
range
),
SymbolViewRoles
::
SymbolRang
e
);
static
const
QChar
prefix
=
QChar
::
fromLatin1
(
'0'
);
line
->
setText
(
QStringLiteral
(
"%1"
).
arg
(
symbol
.
range
.
start
().
line
(),
7
,
10
,
prefix
));
// recurse children
...
...
@@ -407,7 +427,9 @@ public:
m_models
[
0
].
model
=
newModel
;
}
}
else
{
newModel
->
appendRow
(
new
QStandardItem
(
problem
));
auto
item
=
new
QStandardItem
(
problem
);
item
->
setData
(
true
,
SymbolViewRoles
::
IsPlaceholder
);
newModel
->
appendRow
(
item
);
}
// cache detail info with model
...
...
@@ -459,6 +481,8 @@ public:
// current item tracking
updateCurrentTreeItem
();
m_identityModel
->
setSourceModel
(
m_outline
.
get
());
}
void
refresh
(
bool
clear
,
bool
allow_cache
=
true
,
int
retry
=
0
)
...
...
@@ -559,7 +583,7 @@ public:
}
// does the line match our item?
return
item
->
data
(
Qt
::
UserRol
e
).
value
<
KTextEditor
::
Range
>
().
overlapsLine
(
line
)
?
item
:
nullptr
;
return
item
->
data
(
SymbolViewRoles
::
SymbolRang
e
).
value
<
KTextEditor
::
Range
>
().
overlapsLine
(
line
)
?
item
:
nullptr
;
}
void
updateCurrentTreeItem
()
...
...
@@ -588,12 +612,17 @@ public:
void
goToSymbol
(
const
QModelIndex
&
index
)
{
KTextEditor
::
View
*
kv
=
m_mainWindow
->
activeView
();
const
auto
range
=
index
.
data
(
Qt
::
UserRol
e
).
value
<
KTextEditor
::
Range
>
();
const
auto
range
=
index
.
data
(
SymbolViewRoles
::
SymbolRang
e
).
value
<
KTextEditor
::
Range
>
();
if
(
kv
&&
range
.
isValid
())
{
kv
->
setCursorPosition
(
range
.
start
());
}
}
QAbstractItemModel
*
documentSymbolsModel
()
override
{
return
m_identityModel
;
}
private
Q_SLOTS
:
/**
* React on filter change
...
...
@@ -619,9 +648,11 @@ private Q_SLOTS:
}
};
QObject
*
LSPClientSymbolView
::
new_
(
LSPClientPlugin
*
plugin
,
KTextEditor
::
MainWindow
*
mainWin
,
QSharedPointer
<
LSPClientServerManager
>
manager
)
LSPClientSymbolView
*
LSPClientSymbolView
::
new_
(
LSPClientPlugin
*
plugin
,
KTextEditor
::
MainWindow
*
mainWin
,
QSharedPointer
<
LSPClientServerManager
>
manager
)
{
return
new
LSPClientSymbolViewImpl
(
plugin
,
mainWin
,
std
::
move
(
manager
));
}
LSPClientSymbolView
::~
LSPClientSymbolView
()
=
default
;
#include
"lspclientsymbolview.moc"
addons/lspclient/lspclientsymbolview.h
View file @
2fd9db73
...
...
@@ -17,7 +17,11 @@ class LSPClientSymbolView
{
public:
// only needs a factory; no other public interface
static
QObject
*
new_
(
LSPClientPlugin
*
plugin
,
KTextEditor
::
MainWindow
*
mainWin
,
QSharedPointer
<
LSPClientServerManager
>
manager
);
static
LSPClientSymbolView
*
new_
(
LSPClientPlugin
*
plugin
,
KTextEditor
::
MainWindow
*
mainWin
,
QSharedPointer
<
LSPClientServerManager
>
manager
);
virtual
~
LSPClientSymbolView
();
virtual
class
QAbstractItemModel
*
documentSymbolsModel
()
=
0
;
};
class
LSPClientViewTracker
:
public
QObject
...
...
kate/kateurlbar.cpp
View file @
2fd9db73
...
...
@@ -33,6 +33,7 @@
#include
<QStyledItemDelegate>
#include
<QTimer>
#include
<QToolButton>
#include
<QTreeView>
#include
<QUrl>
class
DirFilesModel
:
public
QAbstractListModel
...
...
@@ -213,11 +214,125 @@ private:
DirFilesModel
m_model
;
};
enum
BreadCrumbRole
{
PathRole
=
Qt
::
UserRole
+
1
,
IsSeparator
=
Qt
::
UserRole
+
2
,
class
SymbolsTreeView
:
public
QMenu
{
Q_OBJECT
public:
// Copied from LSPClientSymbolView
enum
Role
{
SymbolRange
=
Qt
::
UserRole
,
ScoreRole
,
//> Unused here
IsPlaceholder
};
SymbolsTreeView
(
QWidget
*
parent
)
:
QMenu
(
parent
)
,
m_tree
(
new
QTreeView
(
this
))
{
m_tree
->
setHorizontalScrollBarPolicy
(
Qt
::
ScrollBarAlwaysOff
);
m_tree
->
setFrameStyle
(
QFrame
::
NoFrame
);
m_tree
->
setUniformRowHeights
(
true
);
m_tree
->
setHeaderHidden
(
true
);
m_tree
->
setTextElideMode
(
Qt
::
ElideRight
);
auto
*
l
=
new
QVBoxLayout
(
this
);
l
->
setContentsMargins
({});
l
->
addWidget
(
m_tree
);
setFocusProxy
(
m_tree
);
m_tree
->
installEventFilter
(
this
);
m_tree
->
viewport
()
->
installEventFilter
(
this
);
connect
(
m_tree
,
&
QTreeView
::
clicked
,
this
,
&
SymbolsTreeView
::
onClicked
);
}
void
setSymbolsModel
(
QAbstractItemModel
*
model
,
KTextEditor
::
View
*
v
,
const
QString
&
text
)
{
m_activeView
=
v
;
m_tree
->
setModel
(
model
);
m_tree
->
expandAll
();
const
auto
idxToSelect
=
model
->
match
(
model
->
index
(
0
,
0
),
0
,
text
,
1
,
Qt
::
MatchExactly
);
if
(
!
idxToSelect
.
isEmpty
())
{
m_tree
->
setCurrentIndex
(
idxToSelect
.
constFirst
());
}
updateGeometry
();
}
bool
eventFilter
(
QObject
*
o
,
QEvent
*
e
)
override
{
// Handling via event filter is necessary to bypass
// QTreeView's own key event handling
if
(
e
->
type
()
==
QEvent
::
KeyPress
)
{
if
(
handleKeyPressEvent
(
static_cast
<
QKeyEvent
*>
(
e
)))
{
return
true
;
}
}
return
QMenu
::
eventFilter
(
o
,
e
);
}
bool
handleKeyPressEvent
(
QKeyEvent
*
ke
)
{
if
(
ke
->
key
()
==
Qt
::
Key_Enter
||
ke
->
key
()
==
Qt
::
Key_Return
)
{
onClicked
(
m_tree
->
currentIndex
());
return
true
;
}
else
if
(
ke
->
key
()
==
Qt
::
Key_Left
)
{
hide
();
Q_EMIT
navigateLeftRight
(
ke
->
key
());
return
false
;
}
else
if
(
ke
->
key
()
==
Qt
::
Key_Escape
)
{
hide
();
ke
->
accept
();
return
true
;
;
}
return
false
;
}
void
updateGeometry
()
{
const
auto
*
model
=
m_tree
->
model
();
const
int
rows
=
model
->
rowCount
();
const
int
rowHeight
=
m_tree
->
sizeHintForRow
(
0
);
const
int
maxHeight
=
rows
*
rowHeight
;
setFixedSize
(
350
,
qMin
(
600
,
maxHeight
));
}
void
onClicked
(
const
QModelIndex
&
idx
)
{
const
auto
range
=
idx
.
data
(
SymbolRange
).
value
<
KTextEditor
::
Range
>
();
if
(
range
.
isValid
())
{
m_activeView
->
setCursorPosition
(
range
.
start
());
}
hide
();
}
static
QModelIndex
symbolForCurrentLine
(
QAbstractItemModel
*
model
,
const
QModelIndex
&
index
,
int
line
)
{
const
int
rowCount
=
model
->
rowCount
(
index
);
for
(
int
i
=
0
;
i
<
rowCount
;
++
i
)
{
const
auto
idx
=
model
->
index
(
i
,
0
,
index
);
if
(
idx
.
data
(
SymbolRange
).
value
<
KTextEditor
::
Range
>
().
overlapsLine
(
line
))
{
return
idx
;
}
else
if
(
model
->
hasChildren
(
idx
))
{
const
auto
childIdx
=
symbolForCurrentLine
(
model
,
idx
,
line
);
if
(
childIdx
.
isValid
())
{
return
childIdx
;
}
}
}
return
{};
}
private:
QTreeView
*
m_tree
;
QPointer
<
KTextEditor
::
View
>
m_activeView
;
Q_SIGNALS:
void
navigateLeftRight
(
int
key
);
};
enum
BreadCrumbRole
{
PathRole
=
Qt
::
UserRole
+
1
,
IsSeparator
,
IsSymbolCrumb
};
class
BreadCrumbDelegate
:
public
QStyledItemDelegate
{
Q_OBJECT
...
...
@@ -301,6 +416,8 @@ public:
const
auto
&
dirs
=
res
;
m_model
.
clear
();
m_symbolsModel
=
nullptr
;
int
i
=
0
;
for
(
const
auto
&
dir
:
dirs
)
{
auto
item
=
new
QStandardItem
(
dir
.
name
);
...
...
@@ -319,6 +436,118 @@ public:
}
i
++
;
}
auto
*
mainWindow
=
m_urlBar
->
viewManager
()
->
mainWindow
();
QPointer
<
QObject
>
lsp
=
mainWindow
->
pluginView
(
QStringLiteral
(
"lspclientplugin"
));
if
(
lsp
)
{
addSymbolCrumb
(
lsp
);
}
}
void
addSymbolCrumb
(
QObject
*
lsp
)
{
QAbstractItemModel
*
model
;
QMetaObject
::
invokeMethod
(
lsp
,
"documentSymbolsModel"
,
Q_RETURN_ARG
(
QAbstractItemModel
*
,
model
));
m_symbolsModel
=
model
;
if
(
!
model
)
{
return
;
}
connect
(
m_symbolsModel
,
&
QAbstractItemModel
::
modelReset
,
this
,
&
BreadCrumbView
::
updateSymbolsCrumb
);
if
(
model
->
rowCount
({})
==
0
)
{
return
;
}
const
auto
view
=
m_urlBar
->
viewManager
()
->
activeView
();
disconnect
(
m_connToView
);
if
(
view
)
{
m_connToView
=
connect
(
view
,
&
KTextEditor
::
View
::
cursorPositionChanged
,
this
,
&
BreadCrumbView
::
updateSymbolsCrumb
);
}
const
auto
idx
=
getSymbolCrumbText
();
if
(
!
idx
.
isValid
())
{
return
;
}
// Add separator
auto
sep
=
new
QStandardItem
(
QIcon
::
fromTheme
(
QStringLiteral
(
"arrow-right"
)),
{});
sep
->
setSelectable
(
false
);
sep
->
setData
(
true
,
BreadCrumbRole
::
IsSeparator
);
m_model
.
appendRow
(
sep
);
const
auto
icon
=
idx
.
data
(
Qt
::
DecorationRole
).
value
<
QIcon
>
();
const
auto
text
=
idx
.
data
().
toString
();
auto
*
item
=
new
QStandardItem
(
icon
,
text
);
item
->
setData
(
true
,
BreadCrumbRole
::
IsSymbolCrumb
);
m_model
.
appendRow
(
item
);
}
void
updateSymbolsCrumb
()
{
QStandardItem
*
item
=
m_model
.
item
(
m_model
.
rowCount
()
-
1
,
0
);
if
(
!
m_urlBar
->
viewSpace
()
->
isActiveSpace
())
{
if
(
item
&&
item
->
data
(
BreadCrumbRole
::
IsSymbolCrumb
).
toBool
())
{
// we are not active viewspace, remove the symbol + separator from breadcrumb
// This is important as LSP only gives us symbols for the current active view
// which atm is in some other viewspace.
// In future we might want to extend LSP to provide us models for documents
// but for now this will do.
qDeleteAll
(
m_model
.
takeRow
(
m_model
.
rowCount
()
-
1
));
qDeleteAll
(
m_model
.
takeRow
(
m_model
.
rowCount
()
-
1
));
}
return
;
}
const
auto
idx
=
getSymbolCrumbText
();
if
(
!
idx
.
isValid
())
{
return
;
}
if
(
!
item
||
!
item
->
data
(
BreadCrumbRole
::
IsSymbolCrumb
).
toBool
())
{
// Add separator
auto
sep
=
new
QStandardItem
(
QIcon
::
fromTheme
(
QStringLiteral
(
"arrow-right"
)),
{});
sep
->
setSelectable
(
false
);
sep
->
setData
(
true
,
BreadCrumbRole
::
IsSeparator
);
m_model
.
appendRow
(
sep
);
item
=
new
QStandardItem
;
m_model
.
appendRow
(
item
);
item
->
setData
(
true
,
BreadCrumbRole
::
IsSymbolCrumb
);
}
const
auto
text
=
idx
.
data
().
toString
();
const
auto
icon
=
idx
.
data
(
Qt
::
DecorationRole
).
value
<
QIcon
>
();
item
->
setText
(
text
);
item
->
setIcon
(
icon
);
}
QModelIndex
getSymbolCrumbText
()
{
if
(
!
m_symbolsModel
)
{
return
{};
}
QModelIndex
first
=
m_symbolsModel
->
index
(
0
,
0
);
if
(
first
.
data
(
SymbolsTreeView
::
IsPlaceholder
).
toBool
())
{
return
{};
}
const
auto
view
=
m_urlBar
->
viewSpace
()
->
currentView
();
int
line
=
view
?
view
->
cursorPosition
().
line
()
:
0
;
QModelIndex
idx
;
if
(
line
>
0
)
{
idx
=
SymbolsTreeView
::
symbolForCurrentLine
(
m_symbolsModel
,
idx
,
line
);
}
else
{
idx
=
first
;
}
if
(
!
idx
.
isValid
())
{
idx
=
first
;
}
return
idx
;
}
static
bool
IsSeparator
(
const
QModelIndex
&
idx
)
...
...
@@ -342,6 +571,24 @@ public:
void
onClicked
(
const
QModelIndex
&
idx
)
{
// Clicked on the symbol?
if
(
m_symbolsModel
&&
idx
.
data
(
BreadCrumbRole
::
IsSymbolCrumb
).
toBool
())
{
auto
activeView
=
m_urlBar
->
viewSpace
()
->
currentView
();
if
(
!
activeView
)
{
// View must be there
return
;
}
SymbolsTreeView
t
(
this
);
connect
(
&
t
,
&
SymbolsTreeView
::
navigateLeftRight
,
this
,
[
this
](
int
k
)
{
onNavigateLeftRight
(
k
,
true
);
});
const
QString
symbolName
=
idx
.
data
().
toString
();
t
.
setSymbolsModel
(
m_symbolsModel
,
activeView
,
symbolName
);
const
auto
pos
=
mapToGlobal
(
rectForIndex
(
idx
).
bottomLeft
());
t
.
setFocus
();
t
.
exec
(
pos
);
}
auto
path
=
idx
.
data
(
BreadCrumbRole
::
PathRole
).
toString
();
if
(
path
.
isEmpty
())
{
return
;
...
...
@@ -427,6 +674,8 @@ private:
KateUrlBar
*
const
m_urlBar
;
QStandardItemModel
m_model
;
QPointer
<
QAbstractItemModel
>
m_symbolsModel
;
QMetaObject
::
Connection
m_connToView
;
// Only one conn at a time
Q_SIGNALS:
void
unsetFocus
();
...
...
@@ -554,6 +803,11 @@ KateViewManager *KateUrlBar::viewManager()
return
m_parentViewSpace
->
viewManger
();
}
KateViewSpace
*
KateUrlBar
::
viewSpace
()
{
return
m_parentViewSpace
;
}
void
KateUrlBar
::
setupLayout
()
{
// Setup the stacked widget
...
...
@@ -569,6 +823,13 @@ void KateUrlBar::setupLayout()
void
KateUrlBar
::
onViewChanged
(
KTextEditor
::
View
*
v
)
{
// We are not active but we have a doc? => don't do anything
// we check for a doc because we want to update the KateUrlBar
// when kate starts
if
(
!
viewSpace
()
->
isActiveSpace
()
&&
m_currentDoc
)
{
return
;
}
if
(
!
v
)
{
updateForDocument
(
nullptr
);
m_untitledDocLabel
->
setText
(
i18n
(
"Untitled"
));
...
...
@@ -607,6 +868,7 @@ void KateUrlBar::updateForDocument(KTextEditor::Document *doc)
if
(
vm
&&
!
vm
->
showUrlNavBar
())
{
return
;
}
m_urlBarView
->
setUrl
(
doc
->
url
());
}
...
...
kate/kateurlbar.h
View file @
2fd9db73
...
...
@@ -18,6 +18,7 @@ public:
void
open
();
class
KateViewManager
*
viewManager
();
class
KateViewSpace
*
viewSpace
();
private:
void
setupLayout
();
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment