Merge branch 'ns-package-aware-autocomplete'

This commit is contained in:
Nate Stemen 2017-11-28 10:24:37 -05:00
commit 2a50a18d23
17 changed files with 1360 additions and 375 deletions

View file

@ -0,0 +1,28 @@
EditorRealTimeController = require "../Editor/EditorRealTimeController"
MetaHandler = require './MetaHandler'
logger = require 'logger-sharelatex'
module.exports = MetaController =
getMetadata: (req, res, next) ->
project_id = req.params.project_id
logger.log {project_id}, "getting all labels for project"
MetaHandler.getAllMetaForProject project_id, (err, projectMeta) ->
if err?
logger.err {project_id, err}, "[MetaController] error getting all labels from project"
return next err
res.json {projectId: project_id, projectMeta: projectMeta}
broadcastMetadataForDoc: (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"
MetaHandler.getMetaForDoc project_id, doc_id, (err, docMeta) ->
if err?
logger.err {project_id, doc_id, err}, "[MetaController] error getting labels from doc"
return next err
EditorRealTimeController.emitToRoom project_id, 'broadcastDocMeta', {
docId: doc_id, meta: docMeta
}
res.sendStatus 200

View file

@ -0,0 +1,66 @@
ProjectEntityHandler = require "../Project/ProjectEntityHandler"
DocumentUpdaterHandler = require '../DocumentUpdater/DocumentUpdaterHandler'
packageMapping = require "./packageMapping"
module.exports = MetaHandler =
labelRegex: () ->
/\\label{(.{0,80}?)}/g
usepackageRegex: () ->
/^\\usepackage(?:\[.{0,80}?])?{(.{0,80}?)}/g
ReqPackageRegex: () ->
/^\\RequirePackage(?:\[.{0,80}?])?{(.{0,80}?)}/g
getAllMetaForProject: (projectId, callback=(err, projectMeta)->) ->
DocumentUpdaterHandler.flushProjectToMongo projectId, (err) ->
if err?
return callback err
ProjectEntityHandler.getAllDocs projectId, (err, docs) ->
if err?
return callback err
projectMeta = MetaHandler.extractMetaFromProjectDocs docs
callback null, projectMeta
getMetaForDoc: (projectId, docId, callback=(err, docMeta)->) ->
DocumentUpdaterHandler.flushDocToMongo projectId, docId, (err) ->
if err?
return callback err
ProjectEntityHandler.getDoc projectId, docId, (err, lines) ->
if err?
return callback err
docMeta = MetaHandler.extractMetaFromDoc lines
callback null, docMeta
extractMetaFromDoc: (lines) ->
docMeta = {labels: [], packages: {}}
packages = []
label_re = MetaHandler.labelRegex()
package_re = MetaHandler.usepackageRegex()
req_package_re = MetaHandler.ReqPackageRegex()
for line in lines
while labelMatch = label_re.exec line
if label = labelMatch[1]
docMeta.labels.push label
while packageMatch = package_re.exec line
if messy = packageMatch[1]
for pkg in messy.split ','
if clean = pkg.trim()
packages.push clean
while packageMatch = req_package_re.exec line
if messy = packageMatch[1]
for pkg in messy.split ','
if clean = pkg.trim()
packages.push clean
for pkg in packages
if packageMapping[pkg]?
docMeta.packages[pkg] = packageMapping[pkg]
return docMeta
extractMetaFromProjectDocs: (projectDocs) ->
projectMeta = {}
for _path, doc of projectDocs
projectMeta[doc._id] = MetaHandler.extractMetaFromDoc doc.lines
return projectMeta

File diff suppressed because one or more lines are too long

View file

@ -43,6 +43,7 @@ SudoModeController = require('./Features/SudoMode/SudoModeController')
SudoModeMiddlewear = require('./Features/SudoMode/SudoModeMiddlewear')
AnalyticsRouter = require('./Features/Analytics/AnalyticsRouter')
AnnouncementsController = require("./Features/Announcements/AnnouncementsController")
MetaController = require('./Features/Metadata/MetaController')
LabelsController = require('./Features/Labels/LabelsController')
TokenAccessController = require('./Features/TokenAccess/TokenAccessController')
Features = require('./infrastructure/Features')
@ -202,6 +203,9 @@ module.exports = class Router
webRouter.get '/Project/:Project_id/download/zip', AuthorizationMiddlewear.ensureUserCanReadProject, ProjectDownloadsController.downloadProject
webRouter.get '/project/download/zip', AuthorizationMiddlewear.ensureUserCanReadMultipleProjects, ProjectDownloadsController.downloadMultipleProjects
webRouter.get '/project/:project_id/metadata', AuthorizationMiddlewear.ensureUserCanReadProject, AuthenticationController.requireLogin(), MetaController.getMetadata
webRouter.post '/project/:project_id/doc/:doc_id/metadata', AuthorizationMiddlewear.ensureUserCanReadProject, AuthenticationController.requireLogin(), MetaController.broadcastMetadataForDoc
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

View file

@ -9,7 +9,7 @@ define [
"ide/pdf/PdfManager"
"ide/binary-files/BinaryFilesManager"
"ide/references/ReferencesManager"
"ide/labels/LabelsManager"
"ide/metadata/MetadataManager"
"ide/review-panel/ReviewPanelManager"
"ide/SafariScrollPatcher"
"ide/AutoCompileOnboardingController",
@ -47,12 +47,12 @@ define [
PdfManager
BinaryFilesManager
ReferencesManager
LabelsManager
MetadataManager
ReviewPanelManager
SafariScrollPatcher
) ->
App.controller "IdeController", ($scope, $timeout, ide, localStorage, sixpack, event_tracking, labels) ->
App.controller "IdeController", ($scope, $timeout, ide, localStorage, sixpack, event_tracking, metadata) ->
# Don't freak out if we're already in an apply callback
$scope.$originalApply = $scope.$apply
$scope.$apply = (fn = () ->) ->
@ -72,10 +72,10 @@ define [
view: "editor"
chatOpen: false
pdfLayout: 'sideBySide'
pdfHidden: false,
pdfWidth: 0,
reviewPanelOpen: localStorage("ui.reviewPanelOpen.#{window.project_id}"),
miniReviewPanelVisible: false,
pdfHidden: false
pdfWidth: 0
reviewPanelOpen: localStorage("ui.reviewPanelOpen.#{window.project_id}")
miniReviewPanelVisible: false
}
$scope.onboarding = {
autoCompile: if window.showAutoCompileOnboarding then 'unseen' else 'dismissed'
@ -138,7 +138,7 @@ define [
ide.pdfManager = new PdfManager(ide, $scope)
ide.permissionsManager = new PermissionsManager(ide, $scope)
ide.binaryFilesManager = new BinaryFilesManager(ide, $scope)
ide.labelsManager = new LabelsManager(ide, $scope, labels)
ide.metadataManager = new MetadataManager(ide, $scope, metadata)
inited = false
$scope.$on "project:joined", () ->
@ -153,7 +153,7 @@ define [
$timeout(
() ->
if $scope.permissions.write
ide.labelsManager.loadProjectLabelsFromServer()
ide.metadataManager.loadProjectMetaFromServer()
_labelsInitialLoadDone = true
, 200
)

View file

@ -9,11 +9,13 @@ 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/metadata/MetadataManager"
"ide/editor/directives/aceEditor/labels/LabelsManager"
"ide/labels/services/labels"
"ide/metadata/services/metadata"
"ide/graphics/services/graphics"
"ide/preamble/services/preamble"
], (App, Ace, SearchBox, ModeList, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightsManager, CursorPositionManager, TrackChangesManager, LabelsManager) ->
], (App, Ace, SearchBox, ModeList, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightsManager, CursorPositionManager, TrackChangesManager, MetadataManager, LabelsManager) ->
EditSession = ace.require('ace/edit_session').EditSession
ModeList = ace.require('ace/ext/modelist')
@ -35,7 +37,7 @@ define [
url = ace.config._moduleUrl(args...) + "?fingerprint=#{window.aceFingerprint}"
return url
App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory, labels, graphics, preamble, $http, $q) ->
App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory, labels, metadata, graphics, preamble, $http, $q) ->
monkeyPatchSearch($rootScope, $compile)
return {
@ -103,7 +105,8 @@ define [
cursorPositionManager = new CursorPositionManager(scope, editor, element, localStorage)
trackChangesManager = new TrackChangesManager(scope, editor, element)
labelsManager = new LabelsManager(scope, editor, element, labels)
autoCompleteManager = new AutoCompleteManager(scope, editor, element, labelsManager, graphics, preamble)
metadataManager = new MetadataManager(scope, editor, element, metadata)
autoCompleteManager = new AutoCompleteManager(scope, editor, element, metadataManager, labelsManager, graphics, preamble)
# Prevert Ctrl|Cmd-S from triggering save dialog

View file

@ -10,8 +10,7 @@ define [
aceSnippetManager = ace.require('ace/snippets').snippetManager
class AutoCompleteManager
constructor: (@$scope, @editor, @element, @labelsManager, @graphics, @preamble) ->
@suggestionManager = new CommandManager()
constructor: (@$scope, @editor, @element, @metadataManager, @labelsManager, @graphics, @preamble) ->
@monkeyPatchAutocomplete()
@ -35,6 +34,8 @@ define [
enableLiveAutocompletion: false
})
commandCompleter = new CommandManager(@metadataManager)
SnippetCompleter = new EnvironmentManager()
PackageCompleter = new PackageManager()
@ -65,7 +66,7 @@ define [
}
callback null, result
labelsManager = @labelsManager
metadataManager = @metadataManager
LabelsCompleter =
getCompletions: (editor, session, pos, prefix, callback) ->
context = Helpers.getContext(editor, pos)
@ -76,13 +77,14 @@ define [
commandName = refMatch[1]
currentArg = refMatch[2]
result = []
result.push {
caption: "\\#{commandName}{}",
snippet: "\\#{commandName}{}",
meta: "cross-reference",
score: 60
}
for label in labelsManager.getAllLabels()
if commandName != 'ref' # ref is in top 100 commands
result.push {
caption: "\\#{commandName}{}",
snippet: "\\#{commandName}{}",
meta: "cross-reference",
score: 60
}
for label in metadataManager.getAllLabels()
result.push {
caption: "\\#{commandName}{#{label}#{if needsClosingBrace then '}' else ''}",
value: "\\#{commandName}{#{label}#{if needsClosingBrace then '}' else ''}",
@ -128,9 +130,9 @@ define [
callback null, result
@editor.completers = [
@suggestionManager
commandCompleter
SnippetCompleter
PackageCompleter
PackageCompleter
ReferencesCompleter
LabelsCompleter
GraphicsCompleter

View file

@ -1,81 +1,8 @@
define [], () ->
noArgumentCommands = [
'item', 'hline', 'lipsum', 'centering', 'noindent', 'textwidth', 'draw',
'maketitle', 'newpage', 'verb', 'bibliography', 'hfill', 'par',
'in', 'sum', 'cdot', 'ldots', 'linewidth', 'left', 'right', 'today',
'clearpage', 'newline', 'endinput', 'tableofcontents', 'vfill',
'bigskip', 'fill', 'cleardoublepage', 'infty', 'leq', 'geq', 'times',
'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'varepsilon', 'zeta',
'eta', 'theta', 'vartheta', 'iota', 'kappa', 'lambda', 'mu', 'nu', 'xi',
'pi', 'varpi', 'rho', 'varrho', 'sigma', 'varsigma', 'tau', 'upsilon',
'phi', 'varphi', 'chi', 'psi', 'omega', 'Gamma', 'Delta', 'Theta',
'Lambda', 'Xi', 'Pi', 'Sigma', 'Upsilon', 'Phi', 'Psi', 'Omega'
]
singleArgumentCommands = [
'chapter', 'section', 'label', 'textbf', 'subsection',
'vspace', 'cite', 'textit', 'documentclass', 'includegraphics', 'input',
'emph','caption', 'ref', 'title', 'author', 'texttt', 'include',
'hspace', 'bibitem', 'url', 'large', 'subsubsection', 'textsc', 'date',
'footnote', 'small', 'thanks', 'underline', 'graphicspath', 'pageref',
'section*', 'subsection*', 'subsubsection*', 'sqrt', 'text',
'normalsize', 'footnotesize', 'Large', 'paragraph', 'pagestyle',
'thispagestyle', 'bibliographystyle', 'hat'
]
doubleArgumentCommands = [
'newcommand', 'frac', 'dfrac', 'renewcommand', 'setlength', 'href',
'newtheorem'
]
tripleArgumentCommands = [
'addcontentsline', 'newacronym', 'multicolumn'
]
special = ['LaTeX', 'TeX']
define [
"./top_hundred_snippets"
], (topHundred) ->
rawCommands = ['usepackage'].concat(
noArgumentCommands,
singleArgumentCommands,
doubleArgumentCommands,
tripleArgumentCommands,
special
)
noArgumentCommands = for cmd in noArgumentCommands
{
caption: "\\#{cmd}"
snippet: "\\#{cmd}"
meta: "cmd"
}
singleArgumentCommands = for cmd in singleArgumentCommands
{
caption: "\\#{cmd}{}"
snippet: "\\#{cmd}{$1}"
meta: "cmd"
}
doubleArgumentCommands = for cmd in doubleArgumentCommands
{
caption: "\\#{cmd}{}{}"
snippet: "\\#{cmd}{$1}{$2}"
meta: "cmd"
}
tripleArgumentCommands = for cmd in tripleArgumentCommands
{
caption: "\\#{cmd}{}{}{}"
snippet: "\\#{cmd}{$1}{$2}{$3}"
meta: "cmd"
}
special = for cmd in special
{
caption: "\\#{cmd}{}"
snippet: "\\#{cmd}{}"
meta: "cmd"
}
staticCommands = [].concat(
noArgumentCommands,
singleArgumentCommands,
doubleArgumentCommands,
tripleArgumentCommands,
special
)
commandNames = (snippet.caption.match(/\w+/)[0] for snippet in topHundred)
class Parser
constructor: (@doc, @prefix) ->
@ -129,10 +56,10 @@ define [], () ->
return realCommands
# Ignore single letter commands since auto complete is moot then.
commandRegex: /\\([a-zA-Z][a-zA-Z]+)/
commandRegex: /\\([a-zA-Z]{2,})/
nextCommand: () ->
i = @doc.search(@commandRegex)
i = @doc.search @commandRegex
if i == -1
return false
else
@ -166,13 +93,21 @@ define [], () ->
return false
class CommandManager
constructor: (@metadataManager) ->
getCompletions: (editor, session, pos, prefix, callback) ->
packages = @metadataManager.getAllPackages()
packageCommands = []
for pkg, snippets of packages
for snippet in snippets
packageCommands.push snippet
doc = session.getValue()
parser = new Parser(doc, prefix)
commands = parser.parse()
completions = []
for command in commands
if command[0] not in rawCommands
if command[0] not in commandNames
caption = "\\#{command[0]}"
score = if caption == prefix then 99 else 50
snippet = caption
@ -191,9 +126,9 @@ define [], () ->
meta: "cmd"
score: score
}
completions = completions.concat staticCommands
completions = completions.concat topHundred, packageCommands
callback(null, completions)
callback null, completions
loadCommandsFromDoc: (doc) ->
parser = new Parser(doc)

View file

@ -0,0 +1,691 @@
define -> [{
"caption": "\\begin{}",
"snippet": "\\begin{$1}",
"meta": "cmd",
"score": 7.849662248028187
}, {
"caption": "\\begin{}[]",
"snippet": "\\begin{$1}[$2]",
"meta": "cmd",
"score": 7.849662248028187
}, {
"caption": "\\begin{}{}",
"snippet": "\\begin{$1}{$2}",
"meta": "cmd",
"score": 7.849662248028187
}, {
"caption": "\\end{}",
"snippet": "\\end{$1}",
"meta": "cmd",
"score": 7.847906405228455
}, {
"caption": "\\usepackage{}",
"snippet": "\\usepackage{$1}",
"meta": "cmd",
"score": 5.427890758130527
}, {
"caption": "\\usepackage[]{}",
"snippet": "\\usepackage[$1]{$2}",
"meta": "cmd",
"score": 5.427890758130527
}, {
"caption": "\\item",
"snippet": "\\item",
"meta": "cmd",
"score": 3.800886892251021
}, {
"caption": "\\item[]",
"snippet": "\\item[$1]",
"meta": "cmd",
"score": 3.800886892251021
}, {
"caption": "\\section{}",
"snippet": "\\section{$1}",
"meta": "cmd",
"score": 3.0952612541683835
}, {
"caption": "\\textbf{}",
"snippet": "\\textbf{$1}",
"meta": "cmd",
"score": 2.627755982816738
}, {
"caption": "\\cite{}",
"snippet": "\\cite{$1}",
"meta": "cmd",
"score": 2.341195220791228
}, {
"caption": "\\label{}",
"snippet": "\\label{$1}",
"meta": "cmd",
"score": 1.897791904799601
}, {
"caption": "\\textit{}",
"snippet": "\\textit{$1}",
"meta": "cmd",
"score": 1.6842996195493385
}, {
"caption": "\\includegraphics[]{}",
"snippet": "\\includegraphics[$1]{$2}",
"meta": "cmd",
"score": 1.4595731795525781
}, {
"caption": "\\documentclass[]{}",
"snippet": "\\documentclass[$1]{$2}",
"meta": "cmd",
"score": 1.4425339817971206
}, {
"caption": "\\documentclass{}",
"snippet": "\\documentclass{$1}",
"meta": "cmd",
"score": 1.4425339817971206
}, {
"caption": "\\frac{}{}",
"snippet": "\\frac{$1}{$2}",
"meta": "cmd",
"score": 1.4341091141105058
}, {
"caption": "\\subsection{}",
"snippet": "\\subsection{$1}",
"meta": "cmd",
"score": 1.3890912739512353
}, {
"caption": "\\hline",
"snippet": "\\hline",
"meta": "cmd",
"score": 1.3209538327406387
}, {
"caption": "\\caption{}",
"snippet": "\\caption{$1}",
"meta": "cmd",
"score": 1.2569477427490174
}, {
"caption": "\\centering",
"snippet": "\\centering",
"meta": "cmd",
"score": 1.1642881814937829
}, {
"caption": "\\vspace{}",
"snippet": "\\vspace{$1}",
"meta": "cmd",
"score": 0.9533807826673939
}, {
"caption": "\\title{}",
"snippet": "\\title{$1}",
"meta": "cmd",
"score": 0.9202908262245683
}, {
"caption": "\\author{}",
"snippet": "\\author{$1}",
"meta": "cmd",
"score": 0.8973590434087177
}, {
"caption": "\\author[]{}",
"snippet": "\\author[$1]{$2}",
"meta": "cmd",
"score": 0.8973590434087177
}, {
"caption": "\\maketitle",
"snippet": "\\maketitle",
"meta": "cmd",
"score": 0.7504160124360846
}, {
"caption": "\\textwidth",
"snippet": "\\textwidth",
"meta": "cmd",
"score": 0.7355328080889112
}, {
"caption": "\\newcommand{}{}",
"snippet": "\\newcommand{$1}{$2}",
"meta": "cmd",
"score": 0.7264891987129375
}, {
"caption": "\\newcommand{}[]{}",
"snippet": "\\newcommand{$1}[$2]{$3}",
"meta": "cmd",
"score": 0.7264891987129375
}, {
"caption": "\\date{}",
"snippet": "\\date{$1}",
"meta": "cmd",
"score": 0.7225518453076786
}, {
"caption": "\\emph{}",
"snippet": "\\emph{$1}",
"meta": "cmd",
"score": 0.7060308784832261
}, {
"caption": "\\textsc{}",
"snippet": "\\textsc{$1}",
"meta": "cmd",
"score": 0.6926466355384758
}, {
"caption": "\\multicolumn{}{}{}",
"snippet": "\\multicolumn{$1}{$2}{$3}",
"meta": "cmd",
"score": 0.5473606021405326
}, {
"caption": "\\input{}",
"snippet": "\\input{$1}",
"meta": "cmd",
"score": 0.4966021927742672
}, {
"caption": "\\alpha",
"snippet": "\\alpha",
"meta": "cmd",
"score": 0.49520006391384913
}, {
"caption": "\\in",
"snippet": "\\in",
"meta": "cmd",
"score": 0.4716039670146658
}, {
"caption": "\\mathbf{}",
"snippet": "\\mathbf{$1}",
"meta": "cmd",
"score": 0.4682018419466319
}, {
"caption": "\\right",
"snippet": "\\right",
"meta": "cmd",
"score": 0.4299239459457309
}, {
"caption": "\\left",
"snippet": "\\left",
"meta": "cmd",
"score": 0.42937815279867964
}, {
"caption": "\\left[]",
"snippet": "\\left[$1]",
"meta": "cmd",
"score": 0.42937815279867964
}, {
"caption": "\\sum",
"snippet": "\\sum",
"meta": "cmd",
"score": 0.42607994509619934
}, {
"caption": "\\noindent",
"snippet": "\\noindent",
"meta": "cmd",
"score": 0.42355747798114207
}, {
"caption": "\\chapter{}",
"snippet": "\\chapter{$1}",
"meta": "cmd",
"score": 0.422097569591803
}, {
"caption": "\\par",
"snippet": "\\par",
"meta": "cmd",
"score": 0.413853376001159
}, {
"caption": "\\lambda",
"snippet": "\\lambda",
"meta": "cmd",
"score": 0.39389600578684125
}, {
"caption": "\\subsubsection{}",
"snippet": "\\subsubsection{$1}",
"meta": "cmd",
"score": 0.3727781330132016
}, {
"caption": "\\bibitem{}",
"snippet": "\\bibitem{$1}",
"meta": "cmd",
"score": 0.3689547570562042
}, {
"caption": "\\bibitem[]{}",
"snippet": "\\bibitem[$1]{$2}",
"meta": "cmd",
"score": 0.3689547570562042
}, {
"caption": "\\text{}",
"snippet": "\\text{$1}",
"meta": "cmd",
"score": 0.3608680734736821
}, {
"caption": "\\setlength{}{}",
"snippet": "\\setlength{$1}{$2}",
"meta": "cmd",
"score": 0.354445763583904
}, {
"caption": "\\setlength",
"snippet": "\\setlength",
"meta": "cmd",
"score": 0.354445763583904
}, {
"caption": "\\mathcal{}",
"snippet": "\\mathcal{$1}",
"meta": "cmd",
"score": 0.35084018920966636
}, {
"caption": "\\newline",
"snippet": "\\newline",
"meta": "cmd",
"score": 0.3311721696201715
}, {
"caption": "\\newpage",
"snippet": "\\newpage",
"meta": "cmd",
"score": 0.3277033727934986
}, {
"caption": "\\renewcommand{}{}",
"snippet": "\\renewcommand{$1}{$2}",
"meta": "cmd",
"score": 0.3267437011085663
}, {
"caption": "\\renewcommand",
"snippet": "\\renewcommand",
"meta": "cmd",
"score": 0.3267437011085663
}, {
"caption": "\\theta",
"snippet": "\\theta",
"meta": "cmd",
"score": 0.3210417159232142
}, {
"caption": "\\hspace{}",
"snippet": "\\hspace{$1}",
"meta": "cmd",
"score": 0.3147206476372336
}, {
"caption": "\\beta",
"snippet": "\\beta",
"meta": "cmd",
"score": 0.3061799530337638
}, {
"caption": "\\texttt{}",
"snippet": "\\texttt{$1}",
"meta": "cmd",
"score": 0.3019066753744355
}, {
"caption": "\\times",
"snippet": "\\times",
"meta": "cmd",
"score": 0.2957960629411553
}, {
"caption": "\\citep{}",
"snippet": "\\citep{$1}",
"meta": "cmd",
"score": 0.2941882834697057
}, {
"caption": "\\color[]{}",
"snippet": "\\color[$1]{$2}",
"meta": "cmd",
"score": 0.2864294797053033
}, {
"caption": "\\color{}",
"snippet": "\\color{$1}",
"meta": "cmd",
"score": 0.2864294797053033
}, {
"caption": "\\mu",
"snippet": "\\mu",
"meta": "cmd",
"score": 0.27635652476799255
}, {
"caption": "\\bibliography{}",
"snippet": "\\bibliography{$1}",
"meta": "cmd",
"score": 0.2659628337907604
}, {
"caption": "\\linewidth",
"snippet": "\\linewidth",
"meta": "cmd",
"score": 0.2639498312518439
}, {
"caption": "\\delta",
"snippet": "\\delta",
"meta": "cmd",
"score": 0.2620578600722735
}, {
"caption": "\\sigma",
"snippet": "\\sigma",
"meta": "cmd",
"score": 0.25940147926344487
}, {
"caption": "\\pi",
"snippet": "\\pi",
"meta": "cmd",
"score": 0.25920934567729714
}, {
"caption": "\\hat{}",
"snippet": "\\hat{$1}",
"meta": "cmd",
"score": 0.25264309033778715
}, {
"caption": "\\hat",
"snippet": "\\hat",
"meta": "cmd",
"score": 0.25264309033778715
}, {
"caption": "\\bibliographystyle{}",
"snippet": "\\bibliographystyle{$1}",
"meta": "cmd",
"score": 0.25122317941387773
}, {
"caption": "\\small",
"snippet": "\\small",
"meta": "cmd",
"score": 0.2447632045426295
}, {
"caption": "\\small{}",
"snippet": "\\small{$1}",
"meta": "cmd",
"score": 0.2447632045426295
}, {
"caption": "\\LaTeX",
"snippet": "\\LaTeX",
"meta": "cmd",
"score": 0.2334089308452787
}, {
"caption": "\\LaTeX{}",
"snippet": "\\LaTeX{$1}",
"meta": "cmd",
"score": 0.2334089308452787
}, {
"caption": "\\cdot",
"snippet": "\\cdot",
"meta": "cmd",
"score": 0.23029085545522762
}, {
"caption": "\\footnote{}",
"snippet": "\\footnote{$1}",
"meta": "cmd",
"score": 0.2253056071787701
}, {
"caption": "\\newtheorem{}[]{}",
"snippet": "\\newtheorem{$1}[$2]{$3}",
"meta": "cmd",
"score": 0.215689795055434
}, {
"caption": "\\newtheorem{}{}",
"snippet": "\\newtheorem{$1}{$2}",
"meta": "cmd",
"score": 0.215689795055434
}, {
"caption": "\\newtheorem{}{}[]",
"snippet": "\\newtheorem{$1}{$2}[$3]",
"meta": "cmd",
"score": 0.215689795055434
}, {
"caption": "\\Delta",
"snippet": "\\Delta",
"meta": "cmd",
"score": 0.21386475063892618
}, {
"caption": "\\tau",
"snippet": "\\tau",
"meta": "cmd",
"score": 0.21236188205859796
}, {
"caption": "\\hfill",
"snippet": "\\hfill",
"meta": "cmd",
"score": 0.2058248088519886
}, {
"caption": "\\leq",
"snippet": "\\leq",
"meta": "cmd",
"score": 0.20498894440637172
}, {
"caption": "\\footnotesize",
"snippet": "\\footnotesize",
"meta": "cmd",
"score": 0.2038592081252624
}, {
"caption": "\\footnotesize{}",
"snippet": "\\footnotesize{$1}",
"meta": "cmd",
"score": 0.2038592081252624
}, {
"caption": "\\large",
"snippet": "\\large",
"meta": "cmd",
"score": 0.20377416734108866
}, {
"caption": "\\large{}",
"snippet": "\\large{$1}",
"meta": "cmd",
"score": 0.20377416734108866
}, {
"caption": "\\sqrt{}",
"snippet": "\\sqrt{$1}",
"meta": "cmd",
"score": 0.20240160977404634
}, {
"caption": "\\epsilon",
"snippet": "\\epsilon",
"meta": "cmd",
"score": 0.2005136761359043
}, {
"caption": "\\Large",
"snippet": "\\Large",
"meta": "cmd",
"score": 0.1987771081149759
}, {
"caption": "\\Large{}",
"snippet": "\\Large{$1}",
"meta": "cmd",
"score": 0.1987771081149759
}, {
"caption": "\\cvitem{}{}",
"snippet": "\\cvitem{$1}{$2}",
"meta": "cmd",
"score": 0.19605476980016281
}, {
"caption": "\\rho",
"snippet": "\\rho",
"meta": "cmd",
"score": 0.1959287380541684
}, {
"caption": "\\omega",
"snippet": "\\omega",
"meta": "cmd",
"score": 0.19326783415115262
}, {
"caption": "\\mathrm{}",
"snippet": "\\mathrm{$1}",
"meta": "cmd",
"score": 0.19117752976172653
}, {
"caption": "\\boldsymbol{}",
"snippet": "\\boldsymbol{$1}",
"meta": "cmd",
"score": 0.18137737738638837
}, {
"caption": "\\boldsymbol",
"snippet": "\\boldsymbol",
"meta": "cmd",
"score": 0.18137737738638837
}, {
"caption": "\\gamma",
"snippet": "\\gamma",
"meta": "cmd",
"score": 0.17940276535431304
}, {
"caption": "\\clearpage",
"snippet": "\\clearpage",
"meta": "cmd",
"score": 0.1789117552185788
}, {
"caption": "\\infty",
"snippet": "\\infty",
"meta": "cmd",
"score": 0.17837290019711305
}, {
"caption": "\\phi",
"snippet": "\\phi",
"meta": "cmd",
"score": 0.17405809173097808
}, {
"caption": "\\partial",
"snippet": "\\partial",
"meta": "cmd",
"score": 0.17168102367966637
}, {
"caption": "\\include{}",
"snippet": "\\include{$1}",
"meta": "cmd",
"score": 0.1547080054979312
}, {
"caption": "\\address{}",
"snippet": "\\address{$1}",
"meta": "cmd",
"score": 0.1525055392611109
}, {
"caption": "\\address[]{}",
"snippet": "\\address[$1]{$2}",
"meta": "cmd",
"score": 0.1525055392611109
}, {
"caption": "\\address{}{}{}",
"snippet": "\\address{$1}{$2}{$3}",
"meta": "cmd",
"score": 0.1525055392611109
}, {
"caption": "\\quad",
"snippet": "\\quad",
"meta": "cmd",
"score": 0.15242755832392743
}, {
"caption": "\\email{}",
"snippet": "\\email{$1}",
"meta": "cmd",
"score": 0.1522294670109857
}, {
"caption": "\\paragraph{}",
"snippet": "\\paragraph{$1}",
"meta": "cmd",
"score": 0.152074250347974
}, {
"caption": "\\varepsilon",
"snippet": "\\varepsilon",
"meta": "cmd",
"score": 0.05411564201390573
}, {
"caption": "\\zeta",
"snippet": "\\zeta",
"meta": "cmd",
"score": 0.023330249803752954
}, {
"caption": "\\eta",
"snippet": "\\eta",
"meta": "cmd",
"score": 0.11088718379889091
}, {
"caption": "\\vartheta",
"snippet": "\\vartheta",
"meta": "cmd",
"score": 0.0025822992078068712
}, {
"caption": "\\iota",
"snippet": "\\iota",
"meta": "cmd",
"score": 0.0024774003791525486
}, {
"caption": "\\kappa",
"snippet": "\\kappa",
"meta": "cmd",
"score": 0.04887876299369008
}, {
"caption": "\\nu",
"snippet": "\\nu",
"meta": "cmd",
"score": 0.09206962821059342
}, {
"caption": "\\xi",
"snippet": "\\xi",
"meta": "cmd",
"score": 0.06496042899265699
}, {
"caption": "\\varpi",
"snippet": "\\varpi",
"meta": "cmd",
"score": 0.0007039358167790341
}, {
"caption": "\\varrho",
"snippet": "\\varrho",
"meta": "cmd",
"score": 0.0011279491613898612
}, {
"caption": "\\varsigma",
"snippet": "\\varsigma",
"meta": "cmd",
"score": 0.0010424880711234978
}, {
"caption": "\\varsigma{}",
"snippet": "\\varsigma{$1}",
"meta": "cmd",
"score": 0.0010424880711234978
}, {
"caption": "\\upsilon",
"snippet": "\\upsilon",
"meta": "cmd",
"score": 0.00420715572598688
}, {
"caption": "\\varphi",
"snippet": "\\varphi",
"meta": "cmd",
"score": 0.03351251516668212
}, {
"caption": "\\chi",
"snippet": "\\chi",
"meta": "cmd",
"score": 0.043373492287805675
}, {
"caption": "\\psi",
"snippet": "\\psi",
"meta": "cmd",
"score": 0.09994508706163642
}, {
"caption": "\\Gamma",
"snippet": "\\Gamma",
"meta": "cmd",
"score": 0.04801549269801977
}, {
"caption": "\\Theta",
"snippet": "\\Theta",
"meta": "cmd",
"score": 0.038090902146599444
}, {
"caption": "\\Lambda",
"snippet": "\\Lambda",
"meta": "cmd",
"score": 0.032206594305977686
}, {
"caption": "\\Xi",
"snippet": "\\Xi",
"meta": "cmd",
"score": 0.01060997225400494
}, {
"caption": "\\Pi",
"snippet": "\\Pi",
"meta": "cmd",
"score": 0.021264671817473237
}, {
"caption": "\\Sigma",
"snippet": "\\Sigma",
"meta": "cmd",
"score": 0.05769642802079917
}, {
"caption": "\\Upsilon",
"snippet": "\\Upsilon",
"meta": "cmd",
"score": 0.00032875192955749566
}, {
"caption": "\\Phi",
"snippet": "\\Phi",
"meta": "cmd",
"score": 0.0538724950042562
}, {
"caption": "\\Psi",
"snippet": "\\Psi",
"meta": "cmd",
"score": 0.03056589143021648
}, {
"caption": "\\Omega",
"snippet": "\\Omega",
"meta": "cmd",
"score": 0.09490387997853639
}]

View file

@ -0,0 +1,85 @@
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
if lineUpToCursor.trim() == '%' or lineUpToCursor.startsWith '\\'
range = new Range(end.row, 0, end.row, end.column + 80)
lineUpToCursor = @editor.getSession().getTextRange range
commandFragment = getLastCommandFragment lineUpToCursor
linesContainPackage = _.any(
change.lines,
(line) -> line.match(/^\\usepackage(?:\[.{0,80}?])?{(.{0,80}?)}/)
)
linesContainReqPackage = _.any(
change.lines,
(line) -> line.match(/^\\RequirePackage(?:\[.{0,80}?])?{(.{0,80}?)}/)
)
linesContainLabel = _.any(
change.lines,
(line) -> line.match(/\\label{(.{0,80}?)}/)
)
linesContainMeta =
linesContainPackage or
linesContainLabel or
linesContainReqPackage
lastCommandFragmentIsLabel = commandFragment?.startsWith '\\label{'
lastCommandFragmentIsPackage = commandFragment?.startsWith '\\usepackage'
lastCommandFragmentIsReqPack = commandFragment?.startsWith '\\RequirePackage'
lastCommandFragmentIsMeta =
lastCommandFragmentIsPackage or
lastCommandFragmentIsLabel or
lastCommandFragmentIsReqPack
if linesContainMeta or lastCommandFragmentIsMeta
@scheduleLoadCurrentDocMetaFromServer()
@editor.on "changeSession", (e) =>
e.oldSession.off "change", onChange
e.session.on "change", onChange
loadDocMetaFromServer: (docId) ->
@Metadata.loadDocMetaFromServer docId
scheduleLoadCurrentDocMetaFromServer: () ->
# De-bounce loading labels with a timeout
currentDocId = @$scope.docId
existingTimeout = @debouncer[currentDocId]
if existingTimeout?
clearTimeout(existingTimeout)
delete @debouncer[currentDocId]
@debouncer[currentDocId] = setTimeout(
() =>
@loadDocMetaFromServer currentDocId
delete @debouncer[currentDocId]
, 1000
, this
)
getAllLabels: () ->
@Metadata.getAllLabels()
getAllPackages: () ->
@Metadata.getAllPackages()

View file

@ -1,5 +1,4 @@
define [
], () ->
define [], () ->
class LabelsManager

View file

@ -0,0 +1,13 @@
define [], () ->
class MetadataManager
constructor: (@ide, @$scope, @metadata) ->
@ide.socket.on 'broadcastDocMeta', (data) =>
@metadata.onBroadcastDocMeta data
@$scope.$on 'entity:deleted', @metadata.onEntityDeleted
@$scope.$on 'file:upload:complete', @metadata.fileUploadComplete
loadProjectMetaFromServer: () ->
@metadata.loadProjectMetaFromServer()

View file

@ -0,0 +1,49 @@
define [
"base"
], (App) ->
App.factory 'metadata', ($http, ide) ->
state = {documents: {}}
metadata = {state: state}
metadata.onBroadcastDocMeta = (data) ->
if data.docId? and data.meta?
state.documents[data.docId] = data.meta
metadata.onEntityDeleted = (e, entity) ->
if entity.type == 'doc'
delete state.documents[entity.id]
metadata.onFileUploadComplete = (e, upload) ->
if upload.entity_type == 'doc'
metadata.loadDocMetaFromServer upload.entity_id
metadata.getAllLabels = () ->
_.flatten(meta.labels for docId, meta of state.documents)
metadata.getAllPackages = () ->
packageCommandMapping = {}
for _docId, meta of state.documents
for packageName, commandSnippets of meta.packages
packageCommandMapping[packageName] = commandSnippets
return packageCommandMapping
metadata.loadProjectMetaFromServer = () ->
$http
.get("/project/#{window.project_id}/metadata")
.then (response) ->
{ data } = response
if data.projectMeta
for docId, docMeta of data.projectMeta
state.documents[docId] = docMeta
metadata.loadDocMetaFromServer = (docId) ->
$http
.post(
"/project/#{window.project_id}/doc/#{docId}/metadata",
{_csrf: window.csrfToken}
)
return metadata

View file

@ -1,119 +0,0 @@
chai = require('chai')
chai.should()
expect = chai.expect
sinon = require("sinon")
modulePath = "../../../../app/js/Features/Labels/LabelsController"
SandboxedModule = require('sandboxed-module')
describe 'LabelsController', ->
beforeEach ->
@projectId = 'somekindofid'
@EditorRealTimeController = {
emitToRoom: sinon.stub()
}
@LabelsHandler = {
getAllLabelsForProject: sinon.stub()
getLabelsForDoc: sinon.stub()
}
@LabelsController = SandboxedModule.require modulePath, requires:
'logger-sharelatex': {log: sinon.stub(), err: sinon.stub()}
'../Editor/EditorRealTimeController': @EditorRealTimeController
'./LabelsHandler': @LabelsHandler
describe 'getAllLabels', ->
beforeEach ->
@fakeLabels = {'somedoc': ['a_label']}
@LabelsHandler.getAllLabelsForProject = sinon.stub().callsArgWith(1, null, @fakeLabels)
@req = {params: {project_id: @projectId}}
@res = {json: sinon.stub()}
@next = sinon.stub()
it 'should call LabelsHandler.getAllLabelsForProject', () ->
@LabelsController.getAllLabels(@req, @res, @next)
@LabelsHandler.getAllLabelsForProject.callCount.should.equal 1
@LabelsHandler.getAllLabelsForProject.calledWith(@projectId).should.equal true
it 'should call not call next with an error', () ->
@LabelsController.getAllLabels(@req, @res, @next)
@next.callCount.should.equal 0
it 'should send a json response', () ->
@LabelsController.getAllLabels(@req, @res, @next)
@res.json.callCount.should.equal 1
expect(@res.json.lastCall.args[0]).to.have.all.keys ['projectId', 'projectLabels']
describe 'when LabelsHandler.getAllLabelsForProject produces an error', ->
beforeEach ->
@LabelsHandler.getAllLabelsForProject = sinon.stub().callsArgWith(1, new Error('woops'))
@req = {params: {project_id: @projectId}}
@res = {json: sinon.stub()}
@next = sinon.stub()
it 'should call LabelsHandler.getAllLabelsForProject', () ->
@LabelsController.getAllLabels(@req, @res, @next)
@LabelsHandler.getAllLabelsForProject.callCount.should.equal 1
@LabelsHandler.getAllLabelsForProject.calledWith(@projectId).should.equal true
it 'should call next with an error', ->
@LabelsController.getAllLabels(@req, @res, @next)
@next.callCount.should.equal 1
expect(@next.lastCall.args[0]).to.be.instanceof Error
it 'should not send a json response', ->
@LabelsController.getAllLabels(@req, @res, @next)
@res.json.callCount.should.equal 0
describe 'broadcastLabelsForDoc', ->
beforeEach ->
@LabelsHandler.getLabelsForDoc = sinon.stub().callsArgWith(2, null, @fakeLabels)
@EditorRealTimeController.emitToRoom = sinon.stub()
@docId = 'somedoc'
@req = {params: {project_id: @projectId, doc_id: @docId}}
@res = {sendStatus: sinon.stub()}
@next = sinon.stub()
it 'should call LabelsHandler.getLabelsForDoc', () ->
@LabelsController.broadcastLabelsForDoc(@req, @res, @next)
@LabelsHandler.getLabelsForDoc.callCount.should.equal 1
@LabelsHandler.getLabelsForDoc.calledWith(@projectId).should.equal true
it 'should call not call next with an error', () ->
@LabelsController.broadcastLabelsForDoc(@req, @res, @next)
@next.callCount.should.equal 0
it 'should send a success response', () ->
@LabelsController.broadcastLabelsForDoc(@req, @res, @next)
@res.sendStatus.callCount.should.equal 1
@res.sendStatus.calledWith(200).should.equal true
it 'should emit a message to room', () ->
@LabelsController.broadcastLabelsForDoc(@req, @res, @next)
@EditorRealTimeController.emitToRoom.callCount.should.equal 1
lastCall = @EditorRealTimeController.emitToRoom.lastCall
expect(lastCall.args[0]).to.equal @projectId
expect(lastCall.args[1]).to.equal 'broadcastDocLabels'
expect(lastCall.args[2]).to.have.all.keys ['docId', 'labels']
describe 'when LabelsHandler.getLabelsForDoc produces an error', ->
beforeEach ->
@LabelsHandler.getLabelsForDoc = sinon.stub().callsArgWith(2, new Error('woops'))
@EditorRealTimeController.emitToRoom = sinon.stub()
@docId = 'somedoc'
@req = {params: {project_id: @projectId, doc_id: @docId}}
@res = {json: sinon.stub()}
@next = sinon.stub()
it 'should call LabelsHandler.getLabelsForDoc', () ->
@LabelsController.broadcastLabelsForDoc(@req, @res, @next)
@LabelsHandler.getLabelsForDoc.callCount.should.equal 1
@LabelsHandler.getLabelsForDoc.calledWith(@projectId).should.equal true
it 'should call next with an error', ->
@LabelsController.broadcastLabelsForDoc(@req, @res, @next)
@next.callCount.should.equal 1
expect(@next.lastCall.args[0]).to.be.instanceof Error
it 'should not send a json response', ->
@LabelsController.broadcastLabelsForDoc(@req, @res, @next)
@res.json.callCount.should.equal 0

View file

@ -1,134 +0,0 @@
chai = require('chai')
chai.should()
expect = chai.expect
sinon = require("sinon")
modulePath = "../../../../app/js/Features/Labels/LabelsHandler"
SandboxedModule = require('sandboxed-module')
describe 'LabelsHandler', ->
beforeEach ->
@projectId = 'someprojectid'
@docId = 'somedocid'
@ProjectEntityHandler = {
getAllDocs: sinon.stub()
getDoc: sinon.stub()
}
@DocumentUpdaterHandler = {
flushDocToMongo: sinon.stub()
}
@LabelsHandler = SandboxedModule.require modulePath, requires:
'../Project/ProjectEntityHandler': @ProjectEntityHandler
'../DocumentUpdater/DocumentUpdaterHandler': @DocumentUpdaterHandler
describe 'extractLabelsFromDoc', ->
beforeEach ->
@lines = [
'one',
'two',
'three \\label{aaa}',
'four five',
'\\label{bbb}',
'six seven'
]
it 'should extract all the labels', ->
docLabels = @LabelsHandler.extractLabelsFromDoc @lines
expect(docLabels).to.deep.equal ['aaa', 'bbb']
describe 'extractLabelsFromProjectDocs', ->
beforeEach ->
@docs = {
'doc_one': {
_id: 'id_one',
lines: ['one', '\\label{aaa} two', 'three']
},
'doc_two': {
_id: 'id_two',
lines: ['four']
},
'doc_three': {
_id: 'id_three',
lines: ['\\label{bbb}', 'five six', 'seven eight \\label{ccc} nine']
}
}
it 'should extract all the labels', ->
projectLabels = @LabelsHandler.extractLabelsFromProjectDocs @docs
expect(projectLabels).to.deep.equal {
'id_one': ['aaa'],
'id_two': [],
'id_three': ['bbb', 'ccc']
}
describe 'getLabelsForDoc', ->
beforeEach ->
@fakeLines = ['one', '\\label{aaa}', 'two']
@fakeLabels = ['aaa']
@DocumentUpdaterHandler.flushDocToMongo = sinon.stub().callsArgWith(2, null)
@ProjectEntityHandler.getDoc = sinon.stub().callsArgWith(2, null, @fakeLines)
@LabelsHandler.extractLabelsFromDoc = sinon.stub().returns(@fakeLabels)
@call = (callback) =>
@LabelsHandler.getLabelsForDoc @projectId, @docId, callback
it 'should not produce an error', (done) ->
@call (err, docLabels) =>
expect(err).to.equal null
done()
it 'should produce docLabels', (done) ->
@call (err, docLabels) =>
expect(docLabels).to.equal @fakeLabels
done()
it 'should call flushDocToMongo', (done) ->
@call (err, docLabels) =>
@DocumentUpdaterHandler.flushDocToMongo.callCount.should.equal 1
@DocumentUpdaterHandler.flushDocToMongo.calledWith(@projectId, @docId).should.equal true
done()
it 'should call getDoc', (done) ->
@call (err, docLabels) =>
@ProjectEntityHandler.getDoc.callCount.should.equal 1
@ProjectEntityHandler.getDoc.calledWith(@projectId, @docId).should.equal true
done()
it 'should call extractLabelsFromDoc', (done) ->
@call (err, docLabels) =>
@LabelsHandler.extractLabelsFromDoc.callCount.should.equal 1
@LabelsHandler.extractLabelsFromDoc.calledWith(@fakeLines).should.equal true
done()
describe 'getAllLabelsForProject', ->
beforeEach ->
@fakeDocs = {
'doc_one': {lines: ['\\label{aaa}']}
}
@fakeLabels = ['aaa']
@DocumentUpdaterHandler.flushProjectToMongo = sinon.stub().callsArgWith(1, null)
@ProjectEntityHandler.getAllDocs = sinon.stub().callsArgWith(1, null, @fakeDocs)
@LabelsHandler.extractLabelsFromProjectDocs = sinon.stub().returns(@fakeLabels)
@call = (callback) =>
@LabelsHandler.getAllLabelsForProject @projectId, callback
it 'should not produce an error', (done) ->
@call (err, projectLabels) =>
expect(err).to.equal null
done()
it 'should produce projectLabels', (done) ->
@call (err, projectLabels) =>
expect(projectLabels).to.equal @fakeLabels
done()
it 'should call getAllDocs', (done) ->
@call (err, projectLabels) =>
@ProjectEntityHandler.getAllDocs.callCount.should.equal 1
@ProjectEntityHandler.getAllDocs.calledWith(@projectId).should.equal true
done()
it 'should call extractLabelsFromDoc', (done) ->
@call (err, docLabels) =>
@LabelsHandler.extractLabelsFromProjectDocs.callCount.should.equal 1
@LabelsHandler.extractLabelsFromProjectDocs.calledWith(@fakeDocs).should.equal true
done()

View file

@ -0,0 +1,119 @@
chai = require('chai')
chai.should()
expect = chai.expect
sinon = require("sinon")
modulePath = "../../../../app/js/Features/Metadata/MetaController"
SandboxedModule = require('sandboxed-module')
describe 'MetaController', ->
beforeEach ->
@projectId = 'somekindofid'
@EditorRealTimeController = {
emitToRoom: sinon.stub()
}
@MetaHandler = {
getAllMetaForProject: sinon.stub()
getMetaForDoc: sinon.stub()
}
@MetadataController = SandboxedModule.require modulePath, requires:
'logger-sharelatex': {log: sinon.stub(), err: sinon.stub()}
'../Editor/EditorRealTimeController': @EditorRealTimeController
'./MetaHandler': @MetaHandler
describe 'getMetadata', ->
beforeEach ->
@fakeLabels = {'somedoc': ['a_label']}
@MetaHandler.getAllMetaForProject = sinon.stub().callsArgWith(1, null, @fakeLabels)
@req = {params: {project_id: @projectId}}
@res = {json: sinon.stub()}
@next = sinon.stub()
it 'should call MetaHandler.getAllMetaForProject', () ->
@MetadataController.getMetadata(@req, @res, @next)
@MetaHandler.getAllMetaForProject.callCount.should.equal 1
@MetaHandler.getAllMetaForProject.calledWith(@projectId).should.equal true
it 'should call not call next with an error', () ->
@MetadataController.getMetadata(@req, @res, @next)
@next.callCount.should.equal 0
it 'should send a json response', () ->
@MetadataController.getMetadata(@req, @res, @next)
@res.json.callCount.should.equal 1
expect(@res.json.lastCall.args[0]).to.have.all.keys ['projectId', 'projectMeta']
describe 'when MetaHandler.getAllMetaForProject produces an error', ->
beforeEach ->
@MetaHandler.getAllMetaForProject = sinon.stub().callsArgWith(1, new Error('woops'))
@req = {params: {project_id: @projectId}}
@res = {json: sinon.stub()}
@next = sinon.stub()
it 'should call MetaHandler.getAllMetaForProject', () ->
@MetadataController.getMetadata(@req, @res, @next)
@MetaHandler.getAllMetaForProject.callCount.should.equal 1
@MetaHandler.getAllMetaForProject.calledWith(@projectId).should.equal true
it 'should call next with an error', ->
@MetadataController.getMetadata(@req, @res, @next)
@next.callCount.should.equal 1
expect(@next.lastCall.args[0]).to.be.instanceof Error
it 'should not send a json response', ->
@MetadataController.getMetadata(@req, @res, @next)
@res.json.callCount.should.equal 0
describe 'broadcastMetadataForDoc', ->
beforeEach ->
@MetaHandler.getMetaForDoc = sinon.stub().callsArgWith(2, null, @fakeLabels)
@EditorRealTimeController.emitToRoom = sinon.stub()
@docId = 'somedoc'
@req = {params: {project_id: @projectId, doc_id: @docId}}
@res = {sendStatus: sinon.stub()}
@next = sinon.stub()
it 'should call MetaHandler.getMetaForDoc', () ->
@MetadataController.broadcastMetadataForDoc(@req, @res, @next)
@MetaHandler.getMetaForDoc.callCount.should.equal 1
@MetaHandler.getMetaForDoc.calledWith(@projectId).should.equal true
it 'should call not call next with an error', () ->
@MetadataController.broadcastMetadataForDoc(@req, @res, @next)
@next.callCount.should.equal 0
it 'should send a success response', () ->
@MetadataController.broadcastMetadataForDoc(@req, @res, @next)
@res.sendStatus.callCount.should.equal 1
@res.sendStatus.calledWith(200).should.equal true
it 'should emit a message to room', () ->
@MetadataController.broadcastMetadataForDoc(@req, @res, @next)
@EditorRealTimeController.emitToRoom.callCount.should.equal 1
lastCall = @EditorRealTimeController.emitToRoom.lastCall
expect(lastCall.args[0]).to.equal @projectId
expect(lastCall.args[1]).to.equal 'broadcastDocMeta'
expect(lastCall.args[2]).to.have.all.keys ['docId', 'meta']
describe 'when MetaHandler.getMetaForDoc produces an error', ->
beforeEach ->
@MetaHandler.getMetaForDoc = sinon.stub().callsArgWith(2, new Error('woops'))
@EditorRealTimeController.emitToRoom = sinon.stub()
@docId = 'somedoc'
@req = {params: {project_id: @projectId, doc_id: @docId}}
@res = {json: sinon.stub()}
@next = sinon.stub()
it 'should call MetaHandler.getMetaForDoc', () ->
@MetadataController.broadcastMetadataForDoc(@req, @res, @next)
@MetaHandler.getMetaForDoc.callCount.should.equal 1
@MetaHandler.getMetaForDoc.calledWith(@projectId).should.equal true
it 'should call next with an error', ->
@MetadataController.broadcastMetadataForDoc(@req, @res, @next)
@next.callCount.should.equal 1
expect(@next.lastCall.args[0]).to.be.instanceof Error
it 'should not send a json response', ->
@MetadataController.broadcastMetadataForDoc(@req, @res, @next)
@res.json.callCount.should.equal 0

View file

@ -0,0 +1,243 @@
chai = require('chai')
chai.should()
expect = chai.expect
sinon = require("sinon")
modulePath = "../../../../app/js/Features/Metadata/MetaHandler"
SandboxedModule = require('sandboxed-module')
describe 'MetaHandler', ->
beforeEach ->
@projectId = 'someprojectid'
@docId = 'somedocid'
@ProjectEntityHandler = {
getAllDocs: sinon.stub()
getDoc: sinon.stub()
}
@DocumentUpdaterHandler = {
flushDocToMongo: sinon.stub()
}
@packageMapping =
foo: [
{
caption: '\\bar'
snippet: '\\bar'
meta: 'foo-cmd'
score: 12
}, {
caption: '\\bat[]{}'
snippet: '\\bar[$1]{$2}'
meta: 'foo-cmd'
score: 10
}
],
baz: [
{
caption: '\\longercommandtest{}'
snippet: '\\longercommandtest{$1}'
meta: 'baz-cmd'
score: 50
}
]
@MetaHandler = SandboxedModule.require modulePath, requires:
'../Project/ProjectEntityHandler': @ProjectEntityHandler
'../DocumentUpdater/DocumentUpdaterHandler': @DocumentUpdaterHandler
'./packageMapping': @packageMapping
describe 'extractMetaFromDoc', ->
beforeEach ->
@lines = [
'\\usepackage{foo}'
'\\usepackage{amsmath, booktabs}'
'one'
'two'
'three \\label{aaa}'
'four five'
'\\label{bbb}'
'six seven'
]
it 'should extract all the labels and packages', ->
docMeta = @MetaHandler.extractMetaFromDoc @lines
expect(docMeta).to.deep.equal {
labels: ['aaa', 'bbb']
packages:
foo: [
{
caption: '\\bar'
snippet: '\\bar'
meta: 'foo-cmd'
score: 12
}, {
caption: '\\bat[]{}'
snippet: '\\bar[$1]{$2}'
meta: 'foo-cmd'
score: 10
}
]
}
describe 'extractMetaFromProjectDocs', ->
beforeEach ->
@docs =
'doc_one':
_id: 'id_one'
lines: ['one', '\\label{aaa} two', 'three']
'doc_two':
_id: 'id_two'
lines: ['four']
'doc_three':
_id: 'id_three'
lines: [
'\\label{bbb}'
'five six'
'seven eight \\label{ccc} nine'
]
'doc_four':
_id: 'id_four'
lines: [
'\\usepackage[width=\\textwidth]{baz}'
'\\usepackage{amsmath}'
]
'doc_five':
_id: 'id_five'
lines: [
'\\usepackage{foo,baz}'
'\\usepackage[options=foo]{hello}'
'some text'
'\\section{this}\\label{sec:intro}'
'In Section \\ref{sec:intro} we saw'
'nothing'
]
it 'should extract all metadata', ->
projectMeta = @MetaHandler.extractMetaFromProjectDocs @docs
expect(projectMeta).to.deep.equal {
'id_one': {labels: ['aaa'], packages: {}}
'id_two': {labels: [], packages: {}}
'id_three': {labels: ['bbb', 'ccc'], packages: {}}
'id_four':
labels: []
packages:
baz: [{
caption: '\\longercommandtest{}'
snippet: '\\longercommandtest{$1}'
meta: 'baz-cmd'
score: 50}]
'id_five':
labels: ['sec:intro']
packages:
foo: [
{
caption: '\\bar'
snippet: '\\bar'
meta: 'foo-cmd'
score: 12
}, {
caption: '\\bat[]{}'
snippet: '\\bar[$1]{$2}'
meta: 'foo-cmd'
score: 10
}
]
baz: [
{
caption: '\\longercommandtest{}'
snippet: '\\longercommandtest{$1}'
meta: 'baz-cmd'
score: 50
}
]
}
describe 'getMetaForDoc', ->
beforeEach ->
@fakeLines = ['\\usepackage{abc}', 'one', '\\label{aaa}', 'two']
@fakeMeta = {labels: ['aaa'], packages: ['abc']}
@DocumentUpdaterHandler.flushDocToMongo = sinon.stub().callsArgWith 2, null
@ProjectEntityHandler.getDoc = sinon.stub().callsArgWith 2, null, @fakeLines
@MetaHandler.extractMetaFromDoc = sinon.stub().returns @fakeMeta
@call = (callback) =>
@MetaHandler.getMetaForDoc @projectId, @docId, callback
it 'should not produce an error', (done) ->
@call (err, docMeta) =>
expect(err).to.equal null
done()
it 'should produce docMeta', (done) ->
@call (err, docMeta) =>
expect(docMeta).to.equal @fakeMeta
done()
it 'should call flushDocToMongo', (done) ->
@call (err, docMeta) =>
@DocumentUpdaterHandler.flushDocToMongo.callCount.should.equal 1
@DocumentUpdaterHandler.flushDocToMongo.calledWith(@projectId, @docId).should.equal true
done()
it 'should call getDoc', (done) ->
@call (err, docMeta) =>
@ProjectEntityHandler.getDoc.callCount.should.equal 1
@ProjectEntityHandler.getDoc.calledWith(@projectId, @docId).should.equal true
done()
it 'should call extractMetaFromDoc', (done) ->
@call (err, docMeta) =>
@MetaHandler.extractMetaFromDoc.callCount.should.equal 1
@MetaHandler.extractMetaFromDoc.calledWith(@fakeLines).should.equal true
done()
describe 'getAllMetaForProject', ->
beforeEach ->
@fakeDocs =
'doc_one':
lines: [
'\\usepackage[some-options,more=foo]{foo}'
'\\label{aaa}'
]
@fakeMeta =
labels: ['aaa']
packages:
foo: [
{
caption: '\\bar'
snippet: '\\bar'
meta: 'foo-cmd'
score: 12
}, {
caption: '\\bat[]{}'
snippet: '\\bar[$1]{$2}'
meta: 'foo-cmd'
score: 10
}
]
@DocumentUpdaterHandler.flushProjectToMongo = sinon.stub().callsArgWith 1, null
@ProjectEntityHandler.getAllDocs = sinon.stub().callsArgWith 1, null, @fakeDocs
@MetaHandler.extractMetaFromProjectDocs = sinon.stub().returns @fakeMeta
@call = (callback) =>
@MetaHandler.getAllMetaForProject @projectId, callback
it 'should not produce an error', (done) ->
@call (err, projectMeta) =>
expect(err).to.equal null
done()
it 'should produce projectMeta', (done) ->
@call (err, projectMeta) =>
expect(projectMeta).to.equal @fakeMeta
done()
it 'should call getAllDocs', (done) ->
@call (err, projectMeta) =>
@ProjectEntityHandler.getAllDocs.callCount.should.equal 1
@ProjectEntityHandler.getAllDocs.calledWith(@projectId).should.equal true
done()
it 'should call extractMetaFromDoc', (done) ->
@call (err, docMeta) =>
@MetaHandler.extractMetaFromProjectDocs.callCount.should.equal 1
@MetaHandler.extractMetaFromProjectDocs.calledWith(@fakeDocs).should.equal true
done()