diff --git a/.travis.yml b/.travis.yml index 4e856402..521036d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ addons: - g++-4.8 - ca-certificates env: - - RACKET_DIR=$HOME/racket RACKET_VERSION=6.1.1 NODE_PATH=/usr/local/lib/node_modules + - RACKET_DIR=$HOME/racket RACKET_VERSION=6.3 NODE_PATH=/usr/local/lib/node_modules before_install: # Get Racket - git clone https://github.com/greghendershott/travis-racket.git >/dev/null diff --git a/src/collects/seashell-cli.rkt b/src/collects/seashell-cli.rkt index 32187103..ad4bf1e2 100644 --- a/src/collects/seashell-cli.rkt +++ b/src/collects/seashell-cli.rkt @@ -12,10 +12,10 @@ (define RUN-TIMEOUT (make-parameter #f)) (define-values (project-dir main-file test-name out-file err-file) (command-line - #:usage-help "Seashell command-line runner. Return codes:\n 10 means failed compilation\n 20 means the program crashed at runtime\n 30 means the program failed its test\n 40 means the program passed its test" + #:usage-help "Seashell command-line tester. Return codes:\n 10 means failed compilation.\n 20 means the program crashed at runtime.\n 30 means the program failed its test.\n 40 means the program passed its test." #:once-each [("-t" "--timeout") timeout - "Override the default seashell timeout (seconds)" + "Override the default seashell timeout (seconds)." (RUN-TIMEOUT (string->number timeout))] #:args (project-dir main-file test-name out-file err-file) (values project-dir main-file test-name out-file err-file))) @@ -23,6 +23,13 @@ (when (RUN-TIMEOUT) (config-set! 'program-run-timeout (RUN-TIMEOUT))) +(define temp-dir-path (make-temporary-file "seashell-runtime-~a" 'directory)) +(define default-exit-handler (exit-handler)) +(exit-handler (lambda (exit-code) + (delete-directory/files temp-dir-path #:must-exist? #f) + (default-exit-handler exit-code))) +(config-set! 'runtime-files-path temp-dir-path) + (define/contract (write-outputs stdout stderr) (-> (or/c bytes? #f) (or/c bytes? #f) void?) (when stdout diff --git a/src/collects/seashell-config.rkt.in b/src/collects/seashell-config.rkt.in index 7ee79553..6a431590 100644 --- a/src/collects/seashell-config.rkt.in +++ b/src/collects/seashell-config.rkt.in @@ -168,6 +168,8 @@ "-gdwarf-4" "-O0" "-mdisable-fp-elim" "-dwarf-column-info" ;; Need this to detect global buffer overflow "-fno-common" + ;; Standard compilation mode + "-std=c99" ; Add standard static analyzer options (clang/lib/Driver/Tools.cpp:2954) ;; "-analyzer-store=region" ;; "-analyzer-opt-analyze-nested-blocks" @@ -185,6 +187,8 @@ (cons 'host '("localhost")) ;; Location of per-user configuration directory. (cons 'seashell (build-path (find-system-path 'home-dir) ".seashell")) + ;; Location of the runtime-files directory. + (cons 'runtime-files-path (build-path (find-system-path 'home-dir) ".seashell" "runtime-files")) ;; Name of credentials file. (cons 'seashell-creds-name (format "seashell~a-creds" @SEASHELL_API_VERSION@)) ;; Name of credentials cookie diff --git a/src/collects/seashell/backend/project.rkt b/src/collects/seashell/backend/project.rkt index 44b59ae2..8f30ef56 100644 --- a/src/collects/seashell/backend/project.rkt +++ b/src/collects/seashell/backend/project.rkt @@ -146,7 +146,7 @@ ;; Gets the path where runtime files are stored. (define/contract (runtime-files-path) (-> path?) - (build-path (read-config 'seashell) "runtime-files")) + (build-path (read-config 'runtime-files-path))) ;; (init-projects) ;; Creates the directories for projects diff --git a/src/collects/seashell/websocket/server.rkt b/src/collects/seashell/websocket/server.rkt index 6cb16d35..351553ad 100644 --- a/src/collects/seashell/websocket/server.rkt +++ b/src/collects/seashell/websocket/server.rkt @@ -18,6 +18,7 @@ ;; along with this program. If not, see . (require racket/contract racket/unit + racket/tcp net/tcp-sig net/url (prefix-in raw: net/tcp-unit) @@ -28,7 +29,6 @@ web-server/private/connection-manager web-server/private/timer racket/async-channel - unstable/contract seashell/websocket/connection seashell/websocket/handshake) @@ -119,7 +119,7 @@ #:tcp@ (unit/c (import) (export tcp^)) #:port - tcp-listen-port? + listen-port-number? #:listen-ip (or/c string? false/c) #:max-waiting diff --git a/src/frontend/css/common.css b/src/frontend/css/common.css index 5e37dd1f..8ed4e031 100644 --- a/src/frontend/css/common.css +++ b/src/frontend/css/common.css @@ -94,7 +94,7 @@ body { font-size: 1.2em; padding-top: 0; padding-left: 0; - padding-right: 5px; + padding-right: 1em; text-decoration: none; font-variant: small-caps; } diff --git a/src/frontend/frontend/directives.js b/src/frontend/frontend/directives.js index 9be4c7bf..668e1b15 100644 --- a/src/frontend/frontend/directives.js +++ b/src/frontend/frontend/directives.js @@ -46,10 +46,27 @@ angular.module('frontend-app') } }; }]) - .directive('focusOn', ['$timeout', function($timeout) { - return function(scope, elem, attr) { - scope.$on(attr.focusOn, function(e) { - $timeout(function () {elem[0].focus();}); + .directive('focusOn', ['$timeout', function ($timeout) { + return function (scope, elem, attr) { + scope.$on(attr.focusOn, function (e) { + $timeout(function () { + elem[0].focus(); }); - }; - }]); + }); + }; + }]) + .directive('focusMe', function ($timeout, $parse) { + return { + link: function (scope, element, attrs) { + var model = $parse(attrs.focusMe); + scope.$watch(model, function (value) { + if (value === true) { + $timeout(function () { + element[0].focus(); + }); + } + }); + } + }; + }); + diff --git a/src/frontend/frontend/file.js b/src/frontend/frontend/file.js index bffe4f28..451082bb 100644 --- a/src/frontend/frontend/file.js +++ b/src/frontend/frontend/file.js @@ -34,7 +34,7 @@ angular.module('frontend-app') self.project = openProject; self.question = openQuestion; self.folder = openFolder; - self.file = openFile; + self.file = openFile; self.console = Console; self.settings = settings; self.undoHistory = undoHistory; @@ -44,6 +44,8 @@ angular.module('frontend-app') self.isBinaryFile = false; self.ready = false; self.ext = self.file.split(".")[1]; + self.runnerFile = false; // true if a runner file is present in the project + self.isFileToRun = false; // true if the current file is the runner file self.editor = null; self.timeout = null; self.loaded = false; @@ -289,7 +291,7 @@ angular.module('frontend-app') self.runFile(); } }, { - combo: 'ctrl+u', + combo: 'ctrl+e', description: "Starts Tests", allowIn: ['INPUT', 'SELECT', 'TEXTAREA'], callback: function (evt) { @@ -425,14 +427,15 @@ angular.module('frontend-app') if(!self.console.PIDs) { return $q.when(); } - return $q.all(_.map(self.console.PIDs, function(id) { + var p = $q.all(_.map(self.console.PIDs, function(id) { return self.project.kill(id); })) .catch(function (error) { errors.report(error, "Could not stop program!"); - self.console.PIDs = null; - self.console.running = false; }); + self.console.running = false; + self.console.PIDs = null; + return p; }; self.indentAll = function() { @@ -483,11 +486,12 @@ angular.module('frontend-app') .then(function () { $scope.$emit('setFileToRun', []); self.runnerFile = true; + self.isFileToRun = true; }) .catch(function (error) { errors.report(error, "Could not set runner file!"); }); - + // emit an event to the parent scope for // since EditorController is in the child scope of EditorFileController @@ -525,11 +529,12 @@ angular.module('frontend-app') function has_ext(ext, fname){ return fname.split(".").pop() === ext; } - + self.refreshRunner = function () { self.project.getFileToRun(self.question) - .then(function (result) { + .then(function (result) { self.runnerFile = (result !== ""); + self.isFileToRun = (result === self.file); }); }; self.refreshRunner(); diff --git a/src/frontend/frontend/modals.js b/src/frontend/frontend/modals.js index 92ab8acd..943a8d2b 100644 --- a/src/frontend/frontend/modals.js +++ b/src/frontend/frontend/modals.js @@ -238,6 +238,52 @@ angular.module('frontend-app') }).result; }; }]) + + .factory('NewTestModal', ['$modal', 'error-service', + function ($modal, errors) { + return function(project, question, notify) { + notify = notify || function () {}; + return $modal.open({ + templateUrl: "frontend/templates/new-test-template.html", + controller: ['$scope', '$state', 'error-service', '$q', + function($scope, $state, errors, $q) { + $scope.new_file_name = ""; + $scope.question = question; + $scope.inputError = false; + $scope.newTest = function () { + // three cases: + // a .in or .expect file, which we create normally + // a file without an extension, for which we create a pair + // an invalid extension + var filename = $scope.new_file_name; + var extension = filename.split('.').pop(); + var results = []; + if (extension === 'in' || extension === 'expect') { + results.push(project.createFile("tests", question, filename)); + } else if (filename.split('.').length < 2) { + // no extension + results.push(project.createFile("tests", question, filename + ".in")); + results.push(project.createFile("tests", question, filename + ".expect")); + } else { + $scope.inputError = "Invalid test file name."; + return false; + } + _.each(results, function (result) { + result.then(function () { + notify(false, true, project, question, $scope.new_file_folder, filename); + }).catch(function (error) { + notify(false, false, project, question, $scope_new_file_folder, filename); + }); + }); + $scope.$close(); + + }; + }] + }).results; + }; + }]) + + // Directive for New Question Modal Service .factory('NewQuestionModal', ['$modal', 'error-service', function ($modal, errors){ diff --git a/src/frontend/frontend/question.js b/src/frontend/frontend/question.js index 1f869401..7c1dfe93 100644 --- a/src/frontend/frontend/question.js +++ b/src/frontend/frontend/question.js @@ -22,10 +22,10 @@ angular.module('frontend-app') // Editor Controller .controller("EditorController", ['$state', 'openQuestion', '$scope', 'error-service', - 'openProject', 'NewFileModal', 'SubmitMarmosetModal', '$interval', 'marmoset', + 'openProject', 'NewFileModal', 'NewTestModal', 'SubmitMarmosetModal', '$interval', 'marmoset', 'NewQuestionModal', 'MarmosetResultsModal', 'console-service', function ($state, openQuestion, $scope, errors, - openProject, newFileModal, submitMarmosetModal, + openProject, newFileModal, newTestModal, submitMarmosetModal, $interval, marmoset, newQuestionModal, marmosetResultsModal, Console) { var self = this; @@ -153,6 +153,14 @@ angular.module('frontend-app') }); }; + /** Adds a pair of .in and .expect files to the project + */ + self.add_test = function () { + newTestModal(self.project, self.question, function () { + self.refresh(); + }); + }; + /** Dispatches a function to run when the * current file is saved. * diff --git a/src/frontend/frontend/templates/new-file-template.html b/src/frontend/frontend/templates/new-file-template.html index 0a610310..7718662a 100644 --- a/src/frontend/frontend/templates/new-file-template.html +++ b/src/frontend/frontend/templates/new-file-template.html @@ -19,6 +19,7 @@ name="new_file_name" class="form-control" placeholder="(file name)" + focus-me="true" style="outline: none; width: 200px; padding-left: 5px"> {{inputError}} diff --git a/src/frontend/frontend/templates/new-project-template.html b/src/frontend/frontend/templates/new-project-template.html index 15a1cb20..3f9d6ec2 100644 --- a/src/frontend/frontend/templates/new-project-template.html +++ b/src/frontend/frontend/templates/new-project-template.html @@ -10,7 +10,7 @@