modified labels service to include packages aware autocompletion

This commit is contained in:
Nate Stemen
2017-10-06 17:15:50 +01:00
parent 507bb568a3
commit cfca4b5d6c
14 changed files with 108 additions and 312 deletions
@@ -8,21 +8,21 @@ module.exports = LabelsController =
getAllLabels: (req, res, next) ->
project_id = req.params.project_id
logger.log {project_id}, "getting all labels for project"
LabelsHandler.getAllLabelsForProject project_id, (err, projectLabels) ->
LabelsHandler.getAllMetaForProject project_id, (err, projectMeta) ->
if err?
logger.err {project_id, err}, "[LabelsController] error getting all labels from project"
return next(err)
res.json {projectId: project_id, projectLabels: projectLabels}
res.json {projectId: project_id, projectMeta: projectMeta}
broadcastLabelsForDoc: (req, res, next) ->
project_id = req.params.project_id
doc_id = req.params.doc_id
logger.log {project_id, doc_id}, "getting labels for doc"
LabelsHandler.getLabelsForDoc project_id, doc_id, (err, docLabels) ->
LabelsHandler.getMetaForDoc project_id, doc_id, (err, docMeta) ->
if err?
logger.err {project_id, doc_id, err}, "[LabelsController] error getting labels from doc"
return next(err)
EditorRealTimeController.emitToRoom project_id, 'broadcastDocLabels', {
docId: doc_id, labels: docLabels
EditorRealTimeController.emitToRoom project_id, 'broadcastDocMeta', {
docId: doc_id, meta: docMeta
}
res.sendStatus(200)
@@ -7,37 +7,45 @@ module.exports = LabelsHandler =
labelCaptureRegex: () ->
/\\label\{([^\}\n\\]{0,80})\}/g
getAllLabelsForProject: (projectId, callback=(err, projectLabels)->) ->
packageCaptureRegex: () ->
/^\\usepackage(?:\[((?:.|\n)*?)])?\s*?{((?:.|\n)*?)}/gm
getAllMetaForProject: (projectId, callback=(err, projectMeta)->) ->
DocumentUpdaterHandler.flushProjectToMongo projectId, (err) ->
if err?
return callback(err)
return callback err
ProjectEntityHandler.getAllDocs projectId, (err, docs) ->
if err?
return callback(err)
projectLabels = LabelsHandler.extractLabelsFromProjectDocs docs
callback(null, projectLabels)
return callback err
projectMeta = LabelsHandler.extractMetaFromProjectDocs docs
callback null, projectMeta
getLabelsForDoc: (projectId, docId, callback=(err, docLabels)->) ->
getMetaForDoc: (projectId, docId, callback=(err, docMeta)->) ->
DocumentUpdaterHandler.flushDocToMongo projectId, docId, (err) ->
if err?
return callback(err)
return callback err
ProjectEntityHandler.getDoc projectId, docId, (err, lines) ->
if err?
return callback(err)
docLabels = LabelsHandler.extractLabelsFromDoc lines
callback(null, docLabels)
return callback err
docMeta = LabelsHandler.extractMetaFromDoc lines
callback null, docMeta
extractLabelsFromDoc: (lines) ->
docLabels = []
extractMetaFromDoc: (lines) ->
docMeta = {labels: [], packages: []}
label_re = LabelsHandler.labelCaptureRegex()
package_re = LabelsHandler.packageCaptureRegex()
for line in lines
re = LabelsHandler.labelCaptureRegex()
while (labelMatch = re.exec(line))
while labelMatch = label_re.exec line
if labelMatch[1]
docLabels.push(labelMatch[1])
return docLabels
docMeta.labels.push labelMatch[1]
while packageMatch = package_re.exec line
if packageMatch[2]
for pkg in packageMatch[2].split ','
docMeta.packages.push pkg.trim()
return docMeta
extractLabelsFromProjectDocs: (projectDocs) ->
projectLabels = {} # docId => List[Label]
extractMetaFromProjectDocs: (projectDocs) ->
projectMeta = {}
for _path, doc of projectDocs
projectLabels[doc._id] = LabelsHandler.extractLabelsFromDoc(doc.lines)
return projectLabels
projectMeta[doc._id] = LabelsHandler.extractMetaFromDoc doc.lines
return projectMeta
@@ -1,60 +0,0 @@
EditorRealTimeController = require "../Editor/EditorRealTimeController"
MetadataHandler = require './MetadataHandler'
logger = require 'logger-sharelatex'
module.exports = MetadataController =
getAllLabels: (req, res, next) ->
project_id = req.params.project_id
logger.log {project_id}, "getting labels for project"
MetadataHandler.getMetadataForProject project_id, (err, projectMetadata) ->
if err?
logger.err {project_id, err}, "[MetadataController] error getting labels from project"
return next err
res.json {
projectId: project_id
projectLabels: projectMetadata["labels"]
}
getAllPackages: (req, res, next) ->
project_id = req.params.project_id
logger.log {project_id}, "getting labels for project"
MetadataHandler.getMetadataForProject project_id, (err, projectMetadata) ->
if err?
logger.err {project_id, err}, "[MetadataController] error getting labels from project"
return next err
res.json {
projectId: project_id
projectPackages: projectMetadata["packages"]
}
getAllMetadata: (req, res, next) ->
project_id = req.params.project_id
logger.log {project_id}, "getting metadata for project"
MetadataHandler.getMetadataForProject project_id, (err, projectMetadata) ->
if err?
logger.err {project_id, err}, "[MetadataController] error getting metadata from project"
return next(err)
res.json {
projectId: project_id
projectLabels: projectMetadata["labels"]
projectPackages: projectMetadata["packages"]
}
broadcastMetadataForDoc: (req, res, next) ->
project_id = req.params.project_id
doc_id = req.params.doc_id
logger.log {project_id, doc_id}, "getting metadata for doc"
MetadataHandler.getMetadataForDoc project_id, doc_id, (err, docMetadata) ->
if err?
logger.err {project_id, doc_id, err}, "[MetadataController] error getting metadata from doc"
return next(err)
EditorRealTimeController.emitToRoom project_id, "broadcastDocMetadata", {
docId: doc_id
metadata: {
labels: docMetadata["labels"]
packages: docMetadata["packages"]
}
}
res.sendStatus 200
@@ -1,50 +0,0 @@
ProjectEntityHandler = require "../Project/ProjectEntityHandler"
DocumentUpdaterHandler = require '../DocumentUpdater/DocumentUpdaterHandler'
module.exports = MetadataHandler =
labelCaptureRegex: () ->
/\\label\{([^\}\n\\]{0,80})\}/g
packageCaptureRegex: () ->
/\\usepackage(?:\[((?:.|\n)*?)])?\s*?{((?:.|\n)*?)}/gm
getMetadataForProject: (projectId, callback=(err, projectMetadata)->) ->
DocumentUpdaterHandler.flushProjectToMongo projectId. (err) ->
if err?
return callback(err)
ProjectEntityHandler.getAllDocs projectId, (err, docs) ->
if err?
return callback(err)
projectMetadata = MetadataHandler.extractMetadataFromProjectDocs docs
callback(null, projectMetadata)
getMetadataForDoc: (projectId, docId, callback=(err, docMetadata)->) ->
DocumentUpdaterHandler.flushDocToMongo projectId, docId, (err) ->
if err?
return callback(err)
ProjectEntityHandler.getDoc projectId, docId, (err, lines) ->
if err?
return callback(err)
docMetadata = MetadataHandler.extractMetadataFromDoc lines
callback(null, docMetadata)
extractMetadataFromProjectDocs: (projectDocs) ->
projectMetadata = {}
for _path, doc of projectDocs
projectMetadata[doc._id] = MetadataHandler.extractMetadataFromDoc doc.lines
return projectMetadata
extractMetadataFromDoc: (lines) ->
docMetadata = {labels: [] packages: []}
label_re = MetadataHandler.labelCaptureRegex()
package_re = MetadataHandler.packageCaptureRegex()
for line in lines # FIXME: usepackage can run over multiple lines
while labelMatch = label_re.exec line
if labelMatch[1]
docMetadata.labels.push labelMatch[1]
while packageMatch = package_re.exec line
if packageMatch[2]
docMetadata.packages.push packageMatch[2]
return docMetadata
+1 -3
View File
@@ -44,6 +44,7 @@ SudoModeMiddlewear = require('./Features/SudoMode/SudoModeMiddlewear')
AnalyticsRouter = require('./Features/Analytics/AnalyticsRouter')
AnnouncementsController = require("./Features/Announcements/AnnouncementsController")
LabelsController = require('./Features/Labels/LabelsController')
# MetadataController = require('./Features/ProjectMetadata/MetadataController')
logger = require("logger-sharelatex")
_ = require("underscore")
@@ -204,9 +205,6 @@ module.exports = class Router
webRouter.get '/project/:project_id/labels', AuthorizationMiddlewear.ensureUserCanReadProject, AuthenticationController.requireLogin(), LabelsController.getAllLabels
webRouter.post '/project/:project_id/doc/:doc_id/labels', AuthorizationMiddlewear.ensureUserCanReadProject, AuthenticationController.requireLogin(), LabelsController.broadcastLabelsForDoc
webRouter.get '/project/:project_id/metadata', AuthorizationMiddlewear.ensureUserCanReadProject, AuthenticationController.requireLogin(), MetadataController.getAllMetadata
webRouter.post '/project/:project_id/doc/:doc_id/metadata'. AuthorizationMiddlewear.ensureUserCanReadProject, AuthenticationController.requireLogin(), MetadataController.broadcastMetadataForDoc
webRouter.get '/tag', AuthenticationController.requireLogin(), TagsController.getAllTags
webRouter.post '/tag', AuthenticationController.requireLogin(), TagsController.createTag
webRouter.post '/tag/:tag_id/rename', AuthenticationController.requireLogin(), TagsController.renameTag
@@ -9,13 +9,11 @@ define [
"ide/editor/directives/aceEditor/highlights/HighlightsManager"
"ide/editor/directives/aceEditor/cursor-position/CursorPositionManager"
"ide/editor/directives/aceEditor/track-changes/TrackChangesManager"
# "ide/editor/directives/aceEditor/labels/LabelsManager"
"ide/editor/directives/aceEditor/metadata/MetadataManager"
# "ide/labels/services/labels"
"ide/metadata/services/metadata"
"ide/editor/directives/aceEditor/labels/LabelsManager"
"ide/labels/services/labels"
"ide/graphics/services/graphics"
"ide/preamble/services/preamble"
], (App, Ace, SearchBox, ModeList, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightsManager, CursorPositionManager, TrackChangesManager, MetadataManager) ->
], (App, Ace, SearchBox, ModeList, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightsManager, CursorPositionManager, TrackChangesManager, LabelsManager) ->
EditSession = ace.require('ace/edit_session').EditSession
ModeList = ace.require('ace/ext/modelist')
@@ -37,7 +35,9 @@ define [
url = ace.config._moduleUrl(args...) + "?fingerprint=#{window.aceFingerprint}"
return url
App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory, metadata, graphics, preamble) ->
# console.log 'making it to right before aceEditor'
App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory, labels, graphics, preamble) ->
monkeyPatchSearch($rootScope, $compile)
return {
@@ -104,9 +104,8 @@ define [
highlightsManager = new HighlightsManager(scope, editor, element)
cursorPositionManager = new CursorPositionManager(scope, editor, element, localStorage)
trackChangesManager = new TrackChangesManager(scope, editor, element)
metadataManager = new metadataManager(scope, editor, element, metadata)
# labelsManager = new LabelsManager(scope, editor, element, labels)
autoCompleteManager = new AutoCompleteManager(scope, editor, element, metadataManager, graphics, preamble)
labelsManager = new LabelsManager(scope, editor, element, labels)
autoCompleteManager = new AutoCompleteManager(scope, editor, element, labelsManager, graphics, preamble)
# Prevert Ctrl|Cmd-S from triggering save dialog
@@ -9,8 +9,8 @@ define [
aceSnippetManager = ace.require('ace/snippets').snippetManager
class AutoCompleteManager
constructor: (@$scope, @editor, @element, @metadataManager, @graphics, @preamble) ->
@suggestionManager = new CommandManager()
constructor: (@$scope, @editor, @element, @labelsManager, @graphics, @preamble) ->
@suggestionManager = new CommandManager(@labelsManager)
@monkeyPatchAutocomplete()
@@ -65,7 +65,7 @@ define [
}
callback null, result
metadataManager = @metadataManager
labelsManager = @labelsManager
LabelsCompleter =
getCompletions: (editor, session, pos, prefix, callback) ->
context = Helpers.getContext(editor, pos)
@@ -76,13 +76,14 @@ define [
commandName = refMatch[1]
currentArg = refMatch[2]
result = []
result.push {
caption: "\\#{commandName}{}",
snippet: "\\#{commandName}{}",
meta: "cross-reference",
score: 60
}
for label in metadataManager.getAllLabels()
if commandName != 'ref' # ref is in top 100 commands
result.push {
caption: "\\#{commandName}{}",
snippet: "\\#{commandName}{}",
meta: "cross-reference",
score: 60
}
for label in labelsManager.getAllLabels()
result.push {
caption: "\\#{commandName}{#{label}#{if needsClosingBrace then '}' else ''}",
value: "\\#{commandName}{#{label}#{if needsClosingBrace then '}' else ''}",
@@ -77,6 +77,11 @@ define [], () ->
special
)
packageCommandMappings = {
amsmath: ['holyshititworks', 'mathematics']
natbib: ['somebibliographystuff']
}
class Parser
constructor: (@doc, @prefix) ->
@@ -166,7 +171,20 @@ define [], () ->
return false
class CommandManager
constructor: (@labelsManager) ->
getCompletions: (editor, session, pos, prefix, callback) ->
packages = @labelsManager.getAllPackages()
packageCommands = []
for pkg in packages
if packageCommandMappings[pkg]?
for cmd in packageCommandMappings[pkg]
packageCommands.push {
caption: "\\#{cmd}"
snippet: "\\#{cmd}"
meta: "cmd"
}
doc = session.getValue()
parser = new Parser(doc, prefix)
commands = parser.parse()
@@ -191,9 +209,9 @@ define [], () ->
meta: "cmd"
score: score
}
completions = completions.concat staticCommands
completions = completions.concat staticCommands, packageCommands
callback(null, completions)
callback null, completions
loadCommandsFromDoc: (doc) ->
parser = new Parser(doc)
@@ -23,9 +23,22 @@ define [
range = new Range(end.row, 0, end.row, end.column)
lineUpToCursor = @editor.getSession().getTextRange(range)
commandFragment = getLastCommandFragment(lineUpToCursor)
linesContainLabel = _.any(change.lines, (line) -> line.match(/\\label\{[^\}\n\\]{0,80}\}/))
lastCommandFragmentIsLabel = commandFragment?.slice(0,7) == '\\label{'
if linesContainLabel or lastCommandFragmentIsLabel
linesContainPackage = _.any(
change.lines,
(line) -> line.match(/\\usepackage(?:\[.*?])?\s*{.*?}/)
)
linesContainLabel = _.any(
change.lines,
(line) -> line.match(/\\label\{[^\}\n\\]{0,80}\}/)
)
linesContainMeta = linesContainPackage or linesContainLabel
lastCommandFragmentIsLabel = commandFragment?.startsWith '\\label{'
lastCommandFragmentIsPackage = commandFragment?.startsWith '\\usepackage'
lastCommandFragmentIsMeta = lastCommandFragmentIsPackage or lastCommandFragmentIsLabel
if linesContainMeta or lastCommandFragmentIsMeta
@scheduleLoadCurrentDocLabelsFromServer()
@editor.on "changeSession", (e) =>
@@ -34,10 +47,10 @@ define [
loadCurrentDocLabelsFromServer: () ->
currentDocId = @$scope.docId
@Labels.loadDocLabelsFromServer(currentDocId)
@Labels.loadDocLabelsFromServer currentDocId
loadDocLabelsFromServer: (docId) ->
@Labels.loadDocLabelsFromServer(docId)
@Labels.loadDocLabelsFromServer docId
scheduleLoadCurrentDocLabelsFromServer: () ->
# De-bounce loading labels with a timeout
@@ -48,7 +61,7 @@ define [
delete @debouncer[currentDocId]
@debouncer[currentDocId] = setTimeout(
() =>
@loadDocLabelsFromServer(currentDocId)
@loadDocLabelsFromServer currentDocId
delete @debouncer[currentDocId]
, 1000
, this
@@ -56,3 +69,6 @@ define [
getAllLabels: () ->
@Labels.getAllLabels()
getAllPackages: () ->
@Labels.getAllPackages()
@@ -1,77 +0,0 @@
define [
"ace/ace"
], () ->
Range = ace.require("ace/range").Range
getLastCommandFragment = (lineUpToCursor) ->
if m = lineUpToCursor.match(/(\\[^\\]+)$/)
return m[1]
else
return null
class MetadataManager
constructor: (@$scope, @editor, @element, @Metadata) ->
@debouncer = {} # DocId => Timeout
onChange = (change) =>
if change.remote
return
if change.action not in ['remove', 'insert']
return
cursorPosition = @editor.getCursorPosition()
end = change.end
range = new Range(end.row, 0, end.row, end.column)
lineUpToCursor = @editor.getSession().getTextRange(range)
commandFragment = getLastCommandFragment(lineUpToCursor)
linesContainLabel = _.any(
change.lines,
(line) -> line.match(/\\label\{[^\}\n\\]{0,80}\}/)
)
linesContainPackage = _.any(
change.lines,
(line) -> line.match(/\\usepackage(?:\[.*?])?\s*{.*?}/)
)
linesContainMeta = linesContainPackage or linesContainLabel
lastCommandFragmentIsLabel = commandFragment?.startsWith '\\label{'
lastCommandFragmentIsPackage = commandFragment?.startsWith '\\usepackage'
lastCommandFragmentIsMeta = lastCommandFragmentIsPackage or lastCommandFragmentIsLabel
if linesContainMeta or lastCommandFragmentIsMeta
@scheduleLoadCurrentDocMetadataFromServer()
@editor.on 'changeSession', (e) =>
e.oldSession.off 'change', onChange
e.session.on 'change', onChange
loadCurrentDocMetadataFromServer: () ->
currentDocId = @$scope.docId
@Metadata.loadDocMetadataFromServer currentDocId
loadDocMetadataFromServer: (docId) ->
@Metadata.loadDocMetadataFromServer docId
scheduleLoadCurrentDocMetadataFromServer: () ->
# De-bounce loading labels with a timeout
currentDocId = @$scope.docId
existingTimeout = @debouncer[currentDocId]
if existingTimeout?
clearTimeout(existingTimeout)
delete @debouncer[currentDocId]
@debouncer[currentDocId] = setTimeout(
() =>
@loadDocMetadataFromServer(currentDocId)
delete @debouncer[currentDocId]
, 1000
, this
)
getAllLabels: () ->
@Metadata.getAllLabels()
getAllPackages: () ->
@Metadata.getAllPackages()
getAllMetadata: () ->
@Metadata.getAllMetadata()
@@ -1,12 +1,11 @@
define [
], () ->
define [], () ->
class LabelsManager
constructor: (@ide, @$scope, @labels) ->
@ide.socket.on 'broadcastDocLabels', (data) =>
@labels.onBroadcastDocLabels(data)
@ide.socket.on 'broadcastDocMeta', (data) =>
@labels.onBroadcastDocLabels data
@$scope.$on 'entity:deleted', @labels.onEntityDeleted
@$scope.$on 'file:upload:complete', @labels.fileUploadComplete
@@ -11,8 +11,8 @@ define [
}
labels.onBroadcastDocLabels = (data) ->
if data.docId and data.labels
state.documents[data.docId] = data.labels
if data.docId? and data.meta?
state.documents[data.docId] = data.meta
labels.onEntityDeleted = (e, entity) ->
if entity.type == 'doc'
@@ -20,19 +20,22 @@ define [
labels.onFileUploadComplete = (e, upload) ->
if upload.entity_type == 'doc'
labels.loadDocLabelsFromServer(upload.entity_id)
labels.loadDocLabelsFromServer upload.entity_id
labels.getAllLabels = () ->
_.flatten(labels for docId, labels of state.documents)
_.flatten(meta.labels for docId, meta of state.documents)
labels.getAllPackages = () ->
_.flatten(meta.packages for docId, meta of state.documents)
labels.loadProjectLabelsFromServer = () ->
$http
.get("/project/#{window.project_id}/labels")
.then (response) ->
{ data } = response
if data.projectLabels
for docId, docLabels of data.projectLabels
state.documents[docId] = docLabels
if data.projectMeta
for docId, docMeta of data.projectMeta
state.documents[docId] = docMeta.labels
labels.loadDocLabelsFromServer = (docId) ->
$http
@@ -1,13 +0,0 @@
define [], () ->
class MetadataManager
constructor: (@ide, @$scope, @metadata) ->
@ide.socket.on 'broadcastDocMetadata', (data) =>
@metadata.onBroadcastDocMetadata(data)
@$scope.$on 'entity:deleted', @metadata.onEntityDeleted
@$scope.$on 'file:upload:complete', @metadata.fileUploadComplete
loadProjectMetadataFromServer: () ->
@metadata.loadProjectMetadataFromServer()
@@ -1,46 +0,0 @@
define [
"base"
], (App) ->
App.factory 'metadata', ($http, ide) ->
state = {documents: {}}
metadata = {
state: state
}
metadata.onBroadcastDocMetadata = (data) ->
if data.docId and data.metadata
state.documents[data.docId] = data.metadata
metadata.onEntityDeleted = (e, entity) ->
if entity.type == 'doc'
delete state.documents[entity.id]
metadata.onFileUploadComplete = (e, upload) ->
if upload.entity_type == 'doc'
metadata.loadDocMetadataFromServer(upload.entity_id)
metadata.getAllMetadata = () ->
labels = _.flatten(meta['labels'] for docId, meta of state.documents)
packages = _.flatten(meta['packages'] for docId, meta of state.documents)
{labels: labels, packages: packages}
metadata.loadProjectMetadataFromServer = () ->
$http
.get("/project/#{window.project_id}/metadata")
.then (response) ->
{ data } = response
if data.projectMetadata
for docId, docMetadata of data.projectMetadata
state.documents[docId] = docMetadata
metadata.loadDocMetadataFromServer = (docId) ->
$http
.post(
"/project/#{window.project_id}/doc/#{docId}/metadata",
{_csrf: window.csrfToken}
)
return metadata