mirror of
https://github.com/apache/zeppelin
synced 2026-05-24 09:38:26 +00:00
add find and replace feature
This commit is contained in:
parent
87705a9324
commit
f269b8aa79
5 changed files with 332 additions and 0 deletions
|
|
@ -164,6 +164,45 @@ limitations under the License.
|
|||
</div>
|
||||
</span>
|
||||
|
||||
<span class="labelBtn btn-group" id="searchGroup" style="vertical-align:middle; display:inline-block;">
|
||||
<button type="button" class="btn btn-default btn-xs dropdown-toggle"
|
||||
data-toggle="dropdown" tooltip-placement="bottom" uib-tooltip="Search code">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu search-dropdown" ng-click="$event.stopPropagation()" style="width: 350px">
|
||||
<li>
|
||||
<div class="input-group input-group-sm search-group">
|
||||
<span class="input-group-addon">Find</span>
|
||||
<input type="text" class="form-control"
|
||||
ng-class="{'no-match': !search.occurrencesExists && search.searchText !== ''}"
|
||||
ng-change="markAllOccurrencesAndHighlightFirst()"
|
||||
ng-click="markAllOccurrences()"
|
||||
ng-model="search.searchText" ng-trim="false"/>
|
||||
<div class="input-group-btn">
|
||||
<button class="btn btn-default" ng-click="prevOccurrence()">
|
||||
<i class="fa fa-angle-left" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-group-btn">
|
||||
<button class="btn btn-default" ng-click="nextOccurrence()">
|
||||
<i class="fa fa-angle-right" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group input-group-sm search-group">
|
||||
<span class="input-group-addon">Replace</span>
|
||||
<input type="text" class="form-control" ng-model="search.replaceText" ng-trim="false"/>
|
||||
<div class="input-group-btn">
|
||||
<button class="btn btn-default" ng-click="replace()">Replace</button>
|
||||
</div>
|
||||
<div class="input-group-btn">
|
||||
<button class="btn btn-default" ng-click="replaceAll()">All</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
|
||||
<!-- put the delete action by itself for your protection -->
|
||||
<span class="labelBtn" style="vertical-align:middle; display:inline-block;">
|
||||
<!-- if the note is in the trash, remove note permanently -->
|
||||
|
|
|
|||
|
|
@ -62,6 +62,16 @@ function NotebookCtrl ($scope, $route, $routeParams, $location, $rootScope,
|
|||
$scope.currentRevision = 'Head'
|
||||
$scope.revisionView = isRevisionPath($location.path())
|
||||
|
||||
$scope.search = {
|
||||
searchText: '',
|
||||
occurrencesExists: false,
|
||||
needHighlightFirst: false,
|
||||
occurrencesHidden: false,
|
||||
replaceText: '',
|
||||
needToSendNextOccurrenceAfterReplace: false
|
||||
}
|
||||
let currentSearchParagraph = 0
|
||||
|
||||
$scope.$on('setConnectedStatus', function (event, param) {
|
||||
if (connectedOnce && param) {
|
||||
initNotebook()
|
||||
|
|
@ -724,6 +734,136 @@ function NotebookCtrl ($scope, $route, $routeParams, $location, $rootScope,
|
|||
angular.element('.permissionsForm select').find('option:not([is-select2="false"])').remove()
|
||||
}
|
||||
|
||||
const markAllOccurrences = function() {
|
||||
$scope.search.occurrencesExists = false
|
||||
$scope.search.occurrencesHidden = false
|
||||
currentSearchParagraph = 0
|
||||
$scope.$broadcast('markAllOccurrences', $scope.search.searchText)
|
||||
}
|
||||
|
||||
$scope.markAllOccurrences = function() {
|
||||
$scope.search.needHighlightFirst = false
|
||||
markAllOccurrences()
|
||||
}
|
||||
|
||||
$scope.markAllOccurrencesAndHighlightFirst = function() {
|
||||
$scope.search.needHighlightFirst = true
|
||||
markAllOccurrences()
|
||||
}
|
||||
|
||||
const sendNextOccurrenceMessage = function() {
|
||||
if (!$scope.search.occurrencesExists) {
|
||||
markAllOccurrences()
|
||||
if (!$scope.search.occurrencesExists) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if ($scope.search.occurrencesHidden) {
|
||||
markAllOccurrences()
|
||||
}
|
||||
$scope.$broadcast('nextOccurrence', $scope.note.paragraphs[currentSearchParagraph].id)
|
||||
}
|
||||
|
||||
const sendPrevOccurrenceMessage = function() {
|
||||
if (!$scope.search.occurrencesExists) {
|
||||
markAllOccurrences()
|
||||
if (!$scope.search.occurrencesExists) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if ($scope.search.occurrencesHidden) {
|
||||
markAllOccurrences()
|
||||
currentSearchParagraph = $scope.note.paragraphs.length - 1
|
||||
}
|
||||
$scope.$broadcast('prevOccurrence', $scope.note.paragraphs[currentSearchParagraph].id)
|
||||
}
|
||||
|
||||
const increaseCurrentSearchParagraph = function() {
|
||||
++currentSearchParagraph
|
||||
if (currentSearchParagraph >= $scope.note.paragraphs.length) {
|
||||
currentSearchParagraph = 0
|
||||
}
|
||||
}
|
||||
|
||||
const decreaseCurrentSearchParagraph = function () {
|
||||
--currentSearchParagraph
|
||||
if (currentSearchParagraph === -1) {
|
||||
currentSearchParagraph = $scope.note.paragraphs.length - 1
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$on('occurrencesExists', function(event) {
|
||||
if ($scope.search.occurrencesExists) {
|
||||
return
|
||||
}
|
||||
$scope.search.occurrencesExists = true
|
||||
if ($scope.search.needHighlightFirst) {
|
||||
sendNextOccurrenceMessage()
|
||||
}
|
||||
})
|
||||
|
||||
$scope.nextOccurrence = function() {
|
||||
sendNextOccurrenceMessage()
|
||||
}
|
||||
|
||||
$scope.$on('noNextOccurrence', function(event) {
|
||||
increaseCurrentSearchParagraph()
|
||||
sendNextOccurrenceMessage()
|
||||
})
|
||||
|
||||
$scope.prevOccurrence = function() {
|
||||
sendPrevOccurrenceMessage()
|
||||
}
|
||||
|
||||
$scope.$on('noPrevOccurrence', function(event) {
|
||||
decreaseCurrentSearchParagraph()
|
||||
sendPrevOccurrenceMessage()
|
||||
})
|
||||
|
||||
$scope.$on('editorClicked', function() {
|
||||
$scope.search.occurrencesHidden = true
|
||||
$scope.$broadcast('unmarkAll')
|
||||
})
|
||||
|
||||
$scope.replace = function() {
|
||||
if (!$scope.search.occurrencesExists) {
|
||||
$scope.markAllOccurrencesAndHighlightFirst()
|
||||
if (!$scope.search.occurrencesExists) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if ($scope.search.occurrencesHidden) {
|
||||
$scope.markAllOccurrencesAndHighlightFirst()
|
||||
return
|
||||
}
|
||||
$scope.$broadcast('replaceCurrent', $scope.search.searchText, $scope.search.replaceText)
|
||||
if ($scope.search.needToSendNextOccurrenceAfterReplace) {
|
||||
sendNextOccurrenceMessage()
|
||||
}
|
||||
}
|
||||
|
||||
$scope.replaceAll = function() {
|
||||
if (!$scope.search.occurrencesExists) {
|
||||
return
|
||||
}
|
||||
if ($scope.search.occurrencesHidden) {
|
||||
$scope.markAllOccurrences()
|
||||
}
|
||||
$scope.$broadcast('replaceAll', $scope.search.searchText, $scope.search.replaceText)
|
||||
$scope.$broadcast('markAllOccurrences', $scope.search.searchText)
|
||||
}
|
||||
|
||||
$scope.$on('noNextOccurrenceAfterReplace', function() {
|
||||
$scope.search.occurrencesExists = false
|
||||
$scope.search.needHighlightFirst = false
|
||||
$scope.search.needToSendNextOccurrenceAfterReplace = false
|
||||
$scope.$broadcast('checkOccurrences')
|
||||
increaseCurrentSearchParagraph()
|
||||
if ($scope.search.occurrencesExists) {
|
||||
$scope.search.needToSendNextOccurrenceAfterReplace = true
|
||||
}
|
||||
})
|
||||
|
||||
$scope.restartInterpreter = function(interpreter) {
|
||||
const thisConfirm = BootstrapDialog.confirm({
|
||||
closable: false,
|
||||
|
|
|
|||
|
|
@ -333,3 +333,27 @@
|
|||
}
|
||||
.noteAction.headroom--unpinned { top: -100px; }
|
||||
.noteAction.headroom--pinned { top: 50px; /** `noteAction` top */ }
|
||||
|
||||
.search-group input.no-match {
|
||||
border: 1px solid red;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.search-group .input-group-addon {
|
||||
min-width: 64px;
|
||||
}
|
||||
|
||||
.search-group input,
|
||||
.search-group .input-group-addon,
|
||||
.search-group .input-group-btn .btn {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.search-dropdown {
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.search-dropdown li {
|
||||
margin: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,12 @@ function ParagraphCtrl ($scope, $rootScope, $route, $window, $routeParams, $loca
|
|||
paragraphText: '',
|
||||
}
|
||||
|
||||
let searchRanges = []
|
||||
const getCurrentRangeDefault = function() {
|
||||
return {id: -1, markerId: -1}
|
||||
}
|
||||
let currentRange = getCurrentRangeDefault()
|
||||
|
||||
let editorSetting = {}
|
||||
// flag that is used to set editor setting on paste percent sign
|
||||
let pastePercentSign = false
|
||||
|
|
@ -801,6 +807,8 @@ function ParagraphCtrl ($scope, $rootScope, $route, $window, $routeParams, $loca
|
|||
|
||||
// remove binding
|
||||
$scope.editor.commands.removeCommand('showSettingsMenu')
|
||||
$scope.editor.commands.removeCommand('find')
|
||||
$scope.editor.commands.removeCommand('replace')
|
||||
|
||||
let isOption = $rootScope.isMac ? 'option' : 'alt'
|
||||
|
||||
|
|
@ -1444,6 +1452,14 @@ function ParagraphCtrl ($scope, $rootScope, $route, $window, $routeParams, $loca
|
|||
$scope.clearParagraphOutput($scope.paragraph)
|
||||
} else if (keyEvent.ctrlKey && keyEvent.altKey && keyCode === 87) { // Ctrl + Alt + w
|
||||
$scope.goToSingleParagraph()
|
||||
} else if (keyEvent.ctrlKey && keyCode === 70) { // Ctrl + f
|
||||
let elem = angular.element('#searchGroup')
|
||||
let openClass = 'open'
|
||||
if (elem.hasClass(openClass)) {
|
||||
elem.removeClass(openClass)
|
||||
} else {
|
||||
elem.addClass(openClass)
|
||||
}
|
||||
} else {
|
||||
noShortcutDefined = true
|
||||
}
|
||||
|
|
@ -1545,4 +1561,116 @@ function ParagraphCtrl ($scope, $rootScope, $route, $window, $routeParams, $loca
|
|||
})
|
||||
}
|
||||
})
|
||||
|
||||
const clearSearchSelection = function() {
|
||||
for (let i = 0; i < searchRanges.length; ++i) {
|
||||
$scope.editor.session.removeMarker(searchRanges[i].markerId)
|
||||
}
|
||||
searchRanges = []
|
||||
if (currentRange.id !== -1) {
|
||||
$scope.editor.session.removeMarker(currentRange.markerId)
|
||||
}
|
||||
currentRange = getCurrentRangeDefault()
|
||||
}
|
||||
|
||||
$scope.onEditorClick = function() {
|
||||
$scope.$emit('editorClicked')
|
||||
}
|
||||
|
||||
$scope.$on('unmarkAll', function() {
|
||||
clearSearchSelection()
|
||||
})
|
||||
|
||||
const markAllOccurrences = function(text) {
|
||||
clearSearchSelection()
|
||||
if (text === '') {
|
||||
return
|
||||
}
|
||||
if ($scope.editor.findAll(text) === 0) {
|
||||
return
|
||||
}
|
||||
let ranges = $scope.editor.selection.getAllRanges()
|
||||
$scope.editor.selection.toSingleRange()
|
||||
$scope.editor.selection.clearSelection()
|
||||
for (let i = 0; i < ranges.length; ++i) {
|
||||
let id = $scope.editor.session.addMarker(ranges[i], 'ace_selected-word', 'text')
|
||||
searchRanges.push({markerId: id, range: ranges[i]})
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$on('markAllOccurrences', function(event, text) {
|
||||
markAllOccurrences(text)
|
||||
if (searchRanges.length > 0) {
|
||||
$scope.$emit('occurrencesExists')
|
||||
}
|
||||
})
|
||||
|
||||
$scope.$on('nextOccurrence', function(event, paragraphId) {
|
||||
if ($scope.paragraph.id !== paragraphId) {
|
||||
return
|
||||
}
|
||||
let highlightedRangeExists = currentRange.id !== -1
|
||||
if (highlightedRangeExists) {
|
||||
$scope.editor.session.removeMarker(currentRange.markerId)
|
||||
currentRange.markerId = -1
|
||||
}
|
||||
++currentRange.id
|
||||
if (currentRange.id >= searchRanges.length) {
|
||||
currentRange.id = -1
|
||||
$scope.$emit('noNextOccurrence')
|
||||
return
|
||||
}
|
||||
currentRange.markerId = $scope.editor.session.addMarker(
|
||||
searchRanges[currentRange.id].range, 'ace_selection', 'text')
|
||||
})
|
||||
|
||||
$scope.$on('prevOccurrence', function(event, paragraphId) {
|
||||
if ($scope.paragraph.id !== paragraphId) {
|
||||
return
|
||||
}
|
||||
let highlightedRangeExists = currentRange.id !== -1
|
||||
if (highlightedRangeExists) {
|
||||
$scope.editor.session.removeMarker(currentRange.markerId)
|
||||
currentRange.markerId = -1
|
||||
}
|
||||
if (currentRange.id === -1) {
|
||||
currentRange.id = searchRanges.length
|
||||
}
|
||||
--currentRange.id
|
||||
if (currentRange.id === -1) {
|
||||
$scope.$emit('noPrevOccurrence')
|
||||
return
|
||||
}
|
||||
currentRange.markerId = $scope.editor.session.addMarker(
|
||||
searchRanges[currentRange.id].range, 'ace_selection', 'text')
|
||||
})
|
||||
|
||||
$scope.$on('replaceCurrent', function(event, from, to) {
|
||||
if (currentRange.id === -1) {
|
||||
return
|
||||
}
|
||||
let numberFromEnd = searchRanges.length - currentRange.id - 1
|
||||
$scope.editor.session.removeMarker(currentRange.markerId)
|
||||
$scope.editor.session.replace(searchRanges[currentRange.id].range, to)
|
||||
markAllOccurrences(from)
|
||||
currentRange.id = searchRanges.length - numberFromEnd
|
||||
if (currentRange.id === searchRanges.length) {
|
||||
currentRange.id = -1
|
||||
$scope.$emit('noNextOccurrenceAfterReplace')
|
||||
} else {
|
||||
currentRange.markerId = $scope.editor.session.addMarker(
|
||||
searchRanges[currentRange.id].range, 'ace_selection', 'text')
|
||||
}
|
||||
})
|
||||
|
||||
$scope.$on('replaceAll', function(event, from, to) {
|
||||
clearSearchSelection()
|
||||
$scope.editor.replaceAll(to, {needle: from})
|
||||
})
|
||||
|
||||
$scope.$on('checkOccurrences', function() {
|
||||
if (searchRanges.length > 0) {
|
||||
$scope.$emit('occurrencesExists')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ limitations under the License.
|
|||
original-text="originalText"
|
||||
on-load="aceLoaded"
|
||||
revision-view="revisionView"
|
||||
ng-click="onEditorClick()"
|
||||
></code-editor>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue