Merge pull request #1122 from AStove/CommandCompletion

Command completion for weechat 2.9
This commit is contained in:
Lorenz Hübschle-Schneider 2020-04-30 11:22:24 +02:00 committed by GitHub
commit 126d7dc67f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 194 additions and 3 deletions

View File

@ -528,6 +528,25 @@ weechat.factory('connection',
});
};
var requestCompletion = function(bufferId, position, data) {
// Prevent requesting completion if bufferId is invalid
if (!bufferId) {
return;
}
return ngWebsockets.send(
weeChat.Protocol.formatCompletion({
buffer: "0x" + bufferId,
position: position,
data: data
})
).then(function(message) {
return new Promise(function (resolve) {
resolve( handlers.handleCompletion(message) );
});
});
};
return {
connect: connect,
@ -538,7 +557,8 @@ weechat.factory('connection',
sendHotlistClearAll: sendHotlistClearAll,
fetchMoreLines: fetchMoreLines,
requestNicklist: requestNicklist,
attemptReconnect: attemptReconnect
attemptReconnect: attemptReconnect,
requestCompletion: requestCompletion
};
}]);
})();

View File

@ -491,6 +491,12 @@ weechat.factory('handlers', ['$rootScope', '$log', 'models', 'plugins', 'notific
});
};
var handleCompletion = function(message) {
var completionInfo = message.objects[0].content[0];
return completionInfo;
};
var eventHandlers = {
_buffer_closing: handleBufferClosing,
_buffer_line_added: handleBufferLineAdded,
@ -529,7 +535,8 @@ weechat.factory('handlers', ['$rootScope', '$log', 'models', 'plugins', 'notific
handleLineInfo: handleLineInfo,
handleHotlistInfo: handleHotlistInfo,
handleNicklist: handleNicklist,
handleBufferInfo: handleBufferInfo
handleBufferInfo: handleBufferInfo,
handleCompletion: handleCompletion
};
}]);

View File

@ -31,6 +31,9 @@ weechat.directive('inputBar', function() {
// Emojify input. E.g. Turn :smile: into the unicode equivalent, but
// don't do replacements in the middle of a word (e.g. std::io::foo)
$scope.inputChanged = function() {
// Cancel any command completion that was still ongoing
commandCompletionInputChanged = true;
var emojiRegex = /^(?:[\uD800-\uDBFF][\uDC00-\uDFFF])+$/, // *only* emoji
changed = false, // whether a segment was modified
inputNode = $scope.getInputNode(),
@ -76,6 +79,13 @@ weechat.directive('inputBar', function() {
};
$scope.completeNick = function() {
if ((models.version[0] == 2 && models.version[1] >= 9 || models.version[0] > 2) &&
$scope.command.startsWith('/') ) {
// We are completing a command, another function will do
// this on WeeChat 2.9 and later
return;
}
// input DOM node
var inputNode = $scope.getInputNode();
@ -108,6 +118,124 @@ weechat.directive('inputBar', function() {
}, 0);
};
var previousInput;
var commandCompletionList;
var commandCompletionAddSpace;
var commandCompletionBaseWord;
var commandCompletionPosition;
var commandCompletionPositionInList;
var commandCompletionInputChanged;
$scope.completeCommand = function(direction) {
if (models.version[0] < 2 || (models.version[0] == 2 && models.version[1] < 9)) {
// Command completion is only supported on WeeChat 2.9+
return;
}
if ( !$scope.command.startsWith('/') ) {
// We are not completing a command, maybe a nick?
return;
}
// Cancel if input changes
commandCompletionInputChanged = false;
// input DOM node
var inputNode = $scope.getInputNode();
// get current caret position
var caretPos = inputNode.selectionStart;
// get current active buffer
var activeBuffer = models.getActiveBuffer();
// Empty input makes $scope.command undefined -- use empty string instead
var input = $scope.command || '';
// This function is for later cycling the list after we got it
var cycleCompletionList = function (direction) {
// Don't do anything, the input has changed before we were able to complete the command
if ( commandCompletionInputChanged ) {
return;
}
// Check if the list has elements and we have not cycled to the end yet
if ( !commandCompletionList || !commandCompletionList[0] ) {
return;
}
// If we are cycling in the other direction, go back two placed in the list
if ( direction === 'backward' ) {
commandCompletionPositionInList -= 2;
if ( commandCompletionPositionInList < 0 ) {
// We have reached the beginning of list and are going backward, so go to the end;
commandCompletionPositionInList = commandCompletionList.length - 1;
}
}
// Check we have not reached the end of the cycle list
if ( commandCompletionList.length <= commandCompletionPositionInList ) {
// We have reached the end of the list, start at the beginning
commandCompletionPositionInList = 0;
}
// Cycle the list
// First remove the word that's to be completed
var commandBeforeReplace = $scope.command.substring(0, commandCompletionPosition - commandCompletionBaseWord.length);
var commandAfterReplace = $scope.command.substring(commandCompletionPosition, $scope.command.length);
var replacedWord = commandCompletionList[commandCompletionPositionInList];
var suffix = commandCompletionAddSpace ? ' ' : '';
// Fill in the new command
$scope.command = commandBeforeReplace + replacedWord + suffix + commandAfterReplace;
// Set the cursor position
var newCursorPos = commandBeforeReplace.length + replacedWord.length + suffix.length;
setTimeout(function() {
inputNode.focus();
inputNode.setSelectionRange(newCursorPos, newCursorPos);
}, 0);
// If there is only one item in the list, we are done, no next cycle
if ( commandCompletionList.length === 1) {
previousInput = '';
return;
}
// Setup for the next cycle
commandCompletionPositionInList++;
commandCompletionBaseWord = replacedWord + suffix;
previousInput = $scope.command + activeBuffer.id;
commandCompletionPosition = newCursorPos;
}
// Check if we have requested this completion info before
if (input + activeBuffer.id !== previousInput) {
// Remeber we requested this input for next time
previousInput = input + activeBuffer.id;
// Ask weechat for the completion list
connection.requestCompletion(activeBuffer.id, caretPos, input).then( function(completionObject) {
// Save the list of completion object, we will only request is once
// and cycle through it as long as the input doesn't change
commandCompletionList = completionObject.list;
commandCompletionAddSpace = completionObject.add_space
commandCompletionBaseWord = completionObject.base_word;
commandCompletionPosition = caretPos;
commandCompletionPositionInList = 0;
}).then( function () {
//after we get the list we can continue with our first cycle
cycleCompletionList(direction);
});
} else {
// Input hasn't changed so we should already have our completion list
cycleCompletionList(direction);
}
};
$rootScope.insertAtCaret = function(toInsert) {
// caret position in the input bar
var inputNode = $scope.getInputNode(),
@ -367,10 +495,18 @@ weechat.directive('inputBar', function() {
}
// Tab -> nick completion
if (code === 9 && !$event.altKey && !$event.ctrlKey) {
if (code === 9 && !$event.altKey && !$event.ctrlKey && !$event.shiftKey) {
$event.preventDefault();
$scope.iterCandidate = tmpIterCandidate;
$scope.completeNick();
$scope.completeCommand('forward');
return true;
}
// Shitft-Tab -> nick completion backward (only commands)
if (code === 9 && !$event.altKey && !$event.ctrlKey && $event.shiftKey) {
$event.preventDefault();
$scope.completeCommand('backward');
return true;
}
@ -606,6 +742,7 @@ weechat.directive('inputBar', function() {
$scope.handleCompleteNickButton = function($event) {
$event.preventDefault();
$scope.completeNick();
$scope.completeCommand('forward');
setTimeout(function() {
$scope.getInputNode().focus();

View File

@ -777,6 +777,33 @@
return WeeChatProtocol._formatCmd(params.id, 'input', parts);
};
/**
* Formats a completion command.
* https://weechat.org/files/doc/stable/weechat_relay_protocol.en.html#command_completion
* @param params Parameters:
* id: command ID (optional)
* buffer: target buffer (mandatory)
* position: position for completion in string (optional)
* data: input data (optional)
* @return Formatted input command string
*/
WeeChatProtocol.formatCompletion = function(params) {
var defaultParams = {
id: null,
position: -1
};
var parts = [];
params = WeeChatProtocol._mergeParams(defaultParams, params);
parts.push(params.buffer);
parts.push(params.position);
if (params.data) {
parts.push(params.data);
}
return WeeChatProtocol._formatCmd(params.id, 'completion', parts);
};
/**
* Formats a sync or a desync command.
*