2021-02-24 05:00:56 +01:00
|
|
|
import _ from "lodash";
|
2020-08-01 03:43:15 +02:00
|
|
|
|
2021-03-16 23:38:59 +01:00
|
|
|
import * as blueslip from "./blueslip";
|
2021-02-24 05:00:56 +01:00
|
|
|
import {FetchStatus} from "./fetch_status";
|
2021-02-28 00:45:40 +01:00
|
|
|
import {Filter} from "./filter";
|
2021-02-28 00:41:04 +01:00
|
|
|
import * as muting from "./muting";
|
2021-03-25 22:35:45 +01:00
|
|
|
import {page_params} from "./page_params";
|
2021-02-28 21:30:38 +01:00
|
|
|
import * as unread from "./unread";
|
2021-02-24 05:00:56 +01:00
|
|
|
import * as util from "./util";
|
2020-07-25 02:02:35 +02:00
|
|
|
|
2021-02-24 05:00:56 +01:00
|
|
|
export class MessageListData {
|
2021-03-24 21:44:43 +01:00
|
|
|
constructor({excludes_muted_topics, filter = new Filter()}) {
|
|
|
|
this.excludes_muted_topics = excludes_muted_topics;
|
2021-05-07 22:34:38 +02:00
|
|
|
this._all_items = [];
|
2020-07-23 00:26:13 +02:00
|
|
|
this._items = [];
|
|
|
|
this._hash = new Map();
|
|
|
|
this._local_only = new Set();
|
|
|
|
this._selected_id = -1;
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-23 00:26:13 +02:00
|
|
|
this.filter = filter;
|
2020-07-23 01:30:13 +02:00
|
|
|
this.fetch_status = new FetchStatus();
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
all_messages() {
|
2018-05-04 12:44:28 +02:00
|
|
|
return this._items;
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
num_items() {
|
2018-05-04 12:44:28 +02:00
|
|
|
return this._items.length;
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
empty() {
|
2018-05-04 12:44:28 +02:00
|
|
|
return this._items.length === 0;
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
first() {
|
2018-05-04 12:44:28 +02:00
|
|
|
return this._items[0];
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
last() {
|
2018-05-04 12:44:28 +02:00
|
|
|
return this._items[this._items.length - 1];
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
select_idx() {
|
2018-05-26 12:29:38 +02:00
|
|
|
if (this._selected_id === -1) {
|
2020-09-24 07:50:36 +02:00
|
|
|
return undefined;
|
2018-05-26 12:29:38 +02:00
|
|
|
}
|
2020-07-02 01:39:34 +02:00
|
|
|
const ids = this._items.map((message) => message.id);
|
2018-05-26 12:29:38 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const i = ids.indexOf(this._selected_id);
|
2018-05-26 12:29:38 +02:00
|
|
|
if (i === -1) {
|
2020-09-24 07:50:36 +02:00
|
|
|
return undefined;
|
2018-05-26 12:29:38 +02:00
|
|
|
}
|
|
|
|
return i;
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-26 12:29:38 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
prev() {
|
2019-11-02 00:06:25 +01:00
|
|
|
const i = this.select_idx();
|
2018-05-26 12:29:38 +02:00
|
|
|
|
|
|
|
if (i === undefined) {
|
2020-09-24 07:50:36 +02:00
|
|
|
return undefined;
|
2018-05-26 12:29:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (i === 0) {
|
2020-09-24 07:50:36 +02:00
|
|
|
return undefined;
|
2018-05-26 12:29:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return this._items[i - 1].id;
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-26 12:29:38 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
next() {
|
2019-11-02 00:06:25 +01:00
|
|
|
const i = this.select_idx();
|
2018-05-26 12:29:38 +02:00
|
|
|
|
|
|
|
if (i === undefined) {
|
2020-09-24 07:50:36 +02:00
|
|
|
return undefined;
|
2018-05-26 12:29:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (i + 1 >= this._items.length) {
|
2020-09-24 07:50:36 +02:00
|
|
|
return undefined;
|
2018-05-26 12:29:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return this._items[i + 1].id;
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-26 12:29:38 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
is_at_end() {
|
2018-05-29 00:18:27 +02:00
|
|
|
if (this._selected_id === -1) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const n = this._items.length;
|
2018-05-29 00:18:27 +02:00
|
|
|
|
|
|
|
if (n === 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const last_msg = this._items[n - 1];
|
2018-05-29 00:18:27 +02:00
|
|
|
|
2018-06-06 18:19:09 +02:00
|
|
|
return last_msg.id === this._selected_id;
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-29 00:18:27 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
nth_most_recent_id(n) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const i = this._items.length - n;
|
2018-05-04 12:44:28 +02:00
|
|
|
if (i < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return this._items[i].id;
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
clear() {
|
2021-05-07 22:34:38 +02:00
|
|
|
this._all_items = [];
|
2018-05-04 12:44:28 +02:00
|
|
|
this._items = [];
|
2020-02-12 06:32:42 +01:00
|
|
|
this._hash.clear();
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
get(id) {
|
2020-10-07 09:17:30 +02:00
|
|
|
id = Number.parseFloat(id);
|
|
|
|
if (Number.isNaN(id)) {
|
2020-09-24 07:50:36 +02:00
|
|
|
return undefined;
|
2018-05-04 12:44:28 +02:00
|
|
|
}
|
2020-02-12 06:32:42 +01:00
|
|
|
return this._hash.get(id);
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
clear_selected_id() {
|
2018-05-04 12:44:28 +02:00
|
|
|
this._selected_id = -1;
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
selected_id() {
|
2018-05-04 12:44:28 +02:00
|
|
|
return this._selected_id;
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
set_selected_id(id) {
|
2018-05-04 12:44:28 +02:00
|
|
|
this._selected_id = id;
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
selected_idx() {
|
2018-05-04 12:44:28 +02:00
|
|
|
return this._lower_bound(this._selected_id);
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
reset_select_to_closest() {
|
2018-05-04 12:44:28 +02:00
|
|
|
this._selected_id = this.closest_id(this._selected_id);
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
is_search() {
|
2018-05-04 12:44:28 +02:00
|
|
|
return this.filter.is_search();
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2020-07-20 22:18:43 +02:00
|
|
|
can_mark_messages_read() {
|
2019-07-10 02:03:41 +02:00
|
|
|
return this.filter.can_mark_messages_read();
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2020-07-20 22:18:43 +02:00
|
|
|
_get_predicate() {
|
2018-05-04 12:44:28 +02:00
|
|
|
// We cache this.
|
|
|
|
if (!this.predicate) {
|
|
|
|
this.predicate = this.filter.predicate();
|
|
|
|
}
|
|
|
|
return this.predicate;
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
valid_non_duplicated_messages(messages) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const predicate = this._get_predicate();
|
2020-07-23 00:26:13 +02:00
|
|
|
return messages.filter((msg) => this.get(msg.id) === undefined && predicate(msg));
|
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
filter_incoming(messages) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const predicate = this._get_predicate();
|
2021-01-23 02:36:54 +01:00
|
|
|
return messages.filter((message) => predicate(message));
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2021-01-25 06:54:00 +01:00
|
|
|
messages_filtered_for_topic_mutes(messages) {
|
|
|
|
if (!this.excludes_muted_topics) {
|
|
|
|
return [...messages];
|
|
|
|
}
|
|
|
|
|
|
|
|
return messages.filter((message) => {
|
|
|
|
if (message.type !== "stream") {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return !muting.is_topic_muted(message.stream_id, message.topic) || message.mentioned;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-05-07 22:34:38 +02:00
|
|
|
messages_filtered_for_user_mutes(messages) {
|
2021-05-05 13:08:49 +02:00
|
|
|
if (this.filter.is_non_huddle_pm()) {
|
|
|
|
// We are in a 1:1 PM narrow, so do not do any filtering.
|
|
|
|
return [...messages];
|
|
|
|
}
|
|
|
|
|
2021-05-07 22:34:38 +02:00
|
|
|
return messages.filter((message) => {
|
|
|
|
if (message.type !== "private") {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
const recipients = util.extract_pm_recipients(message.to_user_ids);
|
|
|
|
if (recipients.length > 1) {
|
|
|
|
// Huddle message
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const recipient_id = Number.parseInt(recipients[0], 10);
|
|
|
|
return !muting.is_user_muted(recipient_id) && !muting.is_user_muted(message.sender_id);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
unmuted_messages(messages) {
|
2021-05-07 22:34:38 +02:00
|
|
|
return this.messages_filtered_for_topic_mutes(
|
|
|
|
this.messages_filtered_for_user_mutes(messages),
|
|
|
|
);
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
message lists: Don't allow user/topic mute message filtering independently.
This basically reverts 4bd7ec7c3699b08655fb3d6ae2a00a19c8a086db and
3a9dfc02e6414089de8ed5cbc85eb69f60454013.
The plan earlier was to have compeletely different codepaths
for user and topic muting, so that we could call seperate
functions in the message list class on receiving the respective
events.
However, this cannot be done, because if we, for example, on
receiving a `muted_users` event, filter `_all_items` based on
just user mutes, and store the result in `_items`, then, that
result may still contain topic-muted messages, which is
undesirable. Hence whenever we filter messages, we must do so
based on both user as well as topic muting.
(The code for the former will be added in further commits.)
So, we will have a single function which will handle updating
the message lists for muting.
2021-05-07 22:13:03 +02:00
|
|
|
update_items_for_muting() {
|
2018-05-04 12:44:28 +02:00
|
|
|
this._items = this.unmuted_messages(this._all_items);
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
first_unread_message_id() {
|
2020-07-02 01:39:34 +02:00
|
|
|
const first_unread = this._items.find((message) => unread.message_unread(message));
|
2018-05-04 12:44:28 +02:00
|
|
|
|
|
|
|
if (first_unread) {
|
|
|
|
return first_unread.id;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if no unread, return the bottom message
|
|
|
|
return this.last().id;
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
add_messages(messages) {
|
2019-11-02 00:06:25 +01:00
|
|
|
let top_messages = [];
|
|
|
|
let bottom_messages = [];
|
|
|
|
let interior_messages = [];
|
2018-05-04 12:44:28 +02:00
|
|
|
|
|
|
|
// If we're initially populating the list, save the messages in
|
|
|
|
// bottom_messages regardless
|
2020-07-23 00:26:13 +02:00
|
|
|
if (this.selected_id() === -1 && this.empty()) {
|
|
|
|
const narrow_messages = this.filter_incoming(messages);
|
|
|
|
bottom_messages = narrow_messages.filter((msg) => !this.get(msg.id));
|
2018-05-04 12:44:28 +02:00
|
|
|
} else {
|
|
|
|
// Filter out duplicates that are already in self, and all messages
|
|
|
|
// that fail our filter predicate
|
2020-07-23 00:26:13 +02:00
|
|
|
messages = this.valid_non_duplicated_messages(messages);
|
2018-05-04 12:44:28 +02:00
|
|
|
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
for (const msg of messages) {
|
2018-05-04 12:44:28 +02:00
|
|
|
// Put messages in correct order on either side of the
|
|
|
|
// message list. This code path assumes that messages
|
|
|
|
// is a (1) sorted, and (2) consecutive block of
|
|
|
|
// messages that belong in this message list; those
|
|
|
|
// facts should be ensured by the caller.
|
2020-07-23 00:26:13 +02:00
|
|
|
if (this.empty() || msg.id > this.last().id) {
|
2018-05-04 12:44:28 +02:00
|
|
|
bottom_messages.push(msg);
|
2020-07-23 00:26:13 +02:00
|
|
|
} else if (msg.id < this.first().id) {
|
2018-05-04 12:44:28 +02:00
|
|
|
top_messages.push(msg);
|
|
|
|
} else {
|
|
|
|
interior_messages.push(msg);
|
|
|
|
}
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
}
|
|
|
|
|
2018-05-14 12:39:34 +02:00
|
|
|
if (interior_messages.length > 0) {
|
2020-07-23 00:26:13 +02:00
|
|
|
interior_messages = this.add_anywhere(interior_messages);
|
2018-05-14 12:39:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (top_messages.length > 0) {
|
2020-07-23 00:26:13 +02:00
|
|
|
top_messages = this.prepend(top_messages);
|
2018-05-14 12:39:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (bottom_messages.length > 0) {
|
2020-07-23 00:26:13 +02:00
|
|
|
bottom_messages = this.append(bottom_messages);
|
2018-05-14 12:39:34 +02:00
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const info = {
|
2020-07-20 22:18:43 +02:00
|
|
|
top_messages,
|
|
|
|
bottom_messages,
|
|
|
|
interior_messages,
|
2018-06-04 22:39:25 +02:00
|
|
|
};
|
|
|
|
|
2018-05-14 12:39:34 +02:00
|
|
|
return info;
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
add_anywhere(messages) {
|
2018-05-14 12:39:34 +02:00
|
|
|
// Caller should have already filtered messages.
|
|
|
|
// This should be used internally when we have
|
|
|
|
// "interior" messages to add and can't optimize
|
|
|
|
// things by only doing prepend or only doing append.
|
2021-01-25 06:54:00 +01:00
|
|
|
|
|
|
|
const viewable_messages = this.unmuted_messages(messages);
|
|
|
|
|
2021-05-07 22:34:38 +02:00
|
|
|
this._all_items = messages.concat(this._all_items);
|
|
|
|
this._all_items.sort((a, b) => a.id - b.id);
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2021-01-25 06:54:00 +01:00
|
|
|
this._items = viewable_messages.concat(this._items);
|
2020-07-02 01:45:54 +02:00
|
|
|
this._items.sort((a, b) => a.id - b.id);
|
2021-01-25 06:54:00 +01:00
|
|
|
|
2018-05-04 12:44:28 +02:00
|
|
|
this._add_to_hash(messages);
|
|
|
|
return viewable_messages;
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
append(messages) {
|
2018-05-04 12:44:28 +02:00
|
|
|
// Caller should have already filtered
|
2021-01-25 06:54:00 +01:00
|
|
|
const viewable_messages = this.unmuted_messages(messages);
|
|
|
|
|
2021-05-07 22:34:38 +02:00
|
|
|
this._all_items = this._all_items.concat(messages);
|
2018-05-04 12:44:28 +02:00
|
|
|
this._items = this._items.concat(viewable_messages);
|
2021-01-25 06:54:00 +01:00
|
|
|
|
2018-05-04 12:44:28 +02:00
|
|
|
this._add_to_hash(messages);
|
|
|
|
return viewable_messages;
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
prepend(messages) {
|
2018-05-04 12:44:28 +02:00
|
|
|
// Caller should have already filtered
|
2021-01-25 06:54:00 +01:00
|
|
|
const viewable_messages = this.unmuted_messages(messages);
|
|
|
|
|
2021-05-07 22:34:38 +02:00
|
|
|
this._all_items = messages.concat(this._all_items);
|
2018-05-04 12:44:28 +02:00
|
|
|
this._items = viewable_messages.concat(this._items);
|
2021-01-25 06:54:00 +01:00
|
|
|
|
2018-05-04 12:44:28 +02:00
|
|
|
this._add_to_hash(messages);
|
|
|
|
return viewable_messages;
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-11-12 22:03:45 +01:00
|
|
|
remove(message_ids) {
|
|
|
|
const msg_ids_to_remove = new Set(message_ids);
|
|
|
|
for (const id of msg_ids_to_remove) {
|
|
|
|
this._hash.delete(id);
|
|
|
|
this._local_only.delete(id);
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
}
|
|
|
|
|
2021-01-23 02:36:54 +01:00
|
|
|
this._items = this._items.filter((msg) => !msg_ids_to_remove.has(msg.id));
|
2021-05-07 22:34:38 +02:00
|
|
|
this._all_items = this._all_items.filter((msg) => !msg_ids_to_remove.has(msg.id));
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
|
|
|
// Returns messages from the given message list in the specified range, inclusive
|
2020-07-20 22:18:43 +02:00
|
|
|
message_range(start, end) {
|
2018-05-04 12:44:28 +02:00
|
|
|
if (start === -1) {
|
|
|
|
blueslip.error("message_range given a start of -1");
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const start_idx = this._lower_bound(start);
|
2020-07-16 23:29:01 +02:00
|
|
|
const end_idx = this._lower_bound(end);
|
2018-05-04 12:44:28 +02:00
|
|
|
return this._items.slice(start_idx, end_idx + 1);
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
|
|
|
// Returns the index where you could insert the desired ID
|
|
|
|
// into the message list, without disrupting the sort order
|
|
|
|
// This takes into account the potentially-unsorted
|
|
|
|
// nature of local message IDs in the message list
|
2020-07-20 22:18:43 +02:00
|
|
|
_lower_bound(id) {
|
2020-07-23 00:26:13 +02:00
|
|
|
const less_func = (msg, ref_id, a_idx) => {
|
|
|
|
if (this._is_localonly_id(msg.id)) {
|
2018-05-04 12:44:28 +02:00
|
|
|
// First non-local message before this one
|
2020-07-23 00:26:13 +02:00
|
|
|
const effective = this._next_nonlocal_message(this._items, a_idx, (idx) => idx - 1);
|
2018-05-04 12:44:28 +02:00
|
|
|
if (effective) {
|
|
|
|
// Turn the 10.02 in [11, 10.02, 12] into 11.02
|
2020-10-07 09:17:30 +02:00
|
|
|
const decimal = Number.parseFloat((msg.id % 1).toFixed(0.02));
|
2019-11-02 00:06:25 +01:00
|
|
|
const effective_id = effective.id + decimal;
|
2018-05-04 12:44:28 +02:00
|
|
|
return effective_id < ref_id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return msg.id < ref_id;
|
2020-07-23 00:26:13 +02:00
|
|
|
};
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-23 00:26:13 +02:00
|
|
|
return util.lower_bound(this._items, id, less_func);
|
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
closest_id(id) {
|
2018-05-04 12:44:28 +02:00
|
|
|
// We directly keep track of local-only messages,
|
|
|
|
// so if we're asked for one that we know we have,
|
|
|
|
// just return it directly
|
2020-02-12 06:34:41 +01:00
|
|
|
if (this._local_only.has(id)) {
|
2018-05-04 12:44:28 +02:00
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const items = this._items;
|
2018-05-04 12:44:28 +02:00
|
|
|
|
|
|
|
if (items.length === 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let closest = this._lower_bound(id);
|
2018-05-04 12:44:28 +02:00
|
|
|
|
|
|
|
if (closest < items.length && id === items[closest].id) {
|
|
|
|
return items[closest].id;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const potential_closest_matches = [];
|
2018-05-04 12:44:28 +02:00
|
|
|
if (closest > 0 && this._is_localonly_id(items[closest - 1].id)) {
|
|
|
|
// Since we treated all blocks of local ids as their left-most-non-local message
|
|
|
|
// for lower_bound purposes, find the real leftmost index (first non-local id)
|
|
|
|
do {
|
|
|
|
potential_closest_matches.push(closest);
|
|
|
|
closest -= 1;
|
|
|
|
} while (closest > 0 && this._is_localonly_id(items[closest - 1].id));
|
|
|
|
}
|
|
|
|
potential_closest_matches.push(closest);
|
|
|
|
|
|
|
|
if (closest === items.length) {
|
|
|
|
closest = closest - 1;
|
|
|
|
} else {
|
|
|
|
// Any of the ids that we skipped over (due to them being local-only) might be the
|
|
|
|
// closest ID to the desired one, in case there is no exact match.
|
|
|
|
potential_closest_matches.unshift(_.last(potential_closest_matches) - 1);
|
2019-11-02 00:06:25 +01:00
|
|
|
let best_match = items[closest].id;
|
2018-05-04 12:44:28 +02:00
|
|
|
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
for (const potential_idx of potential_closest_matches) {
|
2018-05-04 12:44:28 +02:00
|
|
|
if (potential_idx < 0) {
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
continue;
|
2018-05-04 12:44:28 +02:00
|
|
|
}
|
2019-11-02 00:06:25 +01:00
|
|
|
const item = items[potential_idx];
|
2018-05-04 12:44:28 +02:00
|
|
|
|
|
|
|
if (item === undefined) {
|
2020-07-15 01:29:15 +02:00
|
|
|
blueslip.warn("Invalid potential_idx: " + potential_idx);
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
continue;
|
2018-05-04 12:44:28 +02:00
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const potential_match = item.id;
|
2018-05-04 12:44:28 +02:00
|
|
|
// If the potential id is the closest to the requested, save that one
|
|
|
|
if (Math.abs(id - potential_match) < Math.abs(best_match - id)) {
|
|
|
|
best_match = potential_match;
|
|
|
|
closest = potential_idx;
|
|
|
|
}
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
}
|
|
|
|
return items[closest].id;
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
advance_past_messages(msg_ids) {
|
2018-05-04 12:44:28 +02:00
|
|
|
// Start with the current pointer, but then keep advancing the
|
|
|
|
// pointer while the next message's id is in msg_ids. See trac #1555
|
|
|
|
// for more context, but basically we are skipping over contiguous
|
|
|
|
// messages that we have recently visited.
|
2019-11-02 00:06:25 +01:00
|
|
|
let next_msg_id = 0;
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-02-12 06:36:52 +01:00
|
|
|
const id_set = new Set(msg_ids);
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let idx = this.selected_idx() + 1;
|
2018-05-04 12:44:28 +02:00
|
|
|
while (idx < this._items.length) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const msg_id = this._items[idx].id;
|
2020-02-12 06:36:52 +01:00
|
|
|
if (!id_set.has(msg_id)) {
|
2018-05-04 12:44:28 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
next_msg_id = msg_id;
|
|
|
|
idx += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (next_msg_id > 0) {
|
|
|
|
this.set_selected_id(next_msg_id);
|
|
|
|
}
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
_add_to_hash(messages) {
|
2021-01-22 22:29:08 +01:00
|
|
|
for (const elem of messages) {
|
2020-10-07 09:17:30 +02:00
|
|
|
const id = Number.parseFloat(elem.id);
|
|
|
|
if (Number.isNaN(id)) {
|
2020-10-07 11:51:57 +02:00
|
|
|
throw new TypeError("Bad message id");
|
2018-05-04 12:44:28 +02:00
|
|
|
}
|
2020-07-23 00:26:13 +02:00
|
|
|
if (this._is_localonly_id(id)) {
|
|
|
|
this._local_only.add(id);
|
2018-05-04 12:44:28 +02:00
|
|
|
}
|
2020-07-23 00:26:13 +02:00
|
|
|
if (this._hash.has(id)) {
|
2018-05-04 12:44:28 +02:00
|
|
|
blueslip.error("Duplicate message added to MessageListData");
|
2021-01-22 22:29:08 +01:00
|
|
|
continue;
|
2018-05-04 12:44:28 +02:00
|
|
|
}
|
2020-07-23 00:26:13 +02:00
|
|
|
this._hash.set(id, elem);
|
2021-01-22 22:29:08 +01:00
|
|
|
}
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
_is_localonly_id(id) {
|
2018-05-04 12:44:28 +02:00
|
|
|
return id % 1 !== 0;
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
_next_nonlocal_message(item_list, start_index, op) {
|
2019-11-02 00:06:25 +01:00
|
|
|
let cur_idx = start_index;
|
2018-05-04 12:44:28 +02:00
|
|
|
do {
|
|
|
|
cur_idx = op(cur_idx);
|
|
|
|
} while (item_list[cur_idx] !== undefined && this._is_localonly_id(item_list[cur_idx].id));
|
|
|
|
return item_list[cur_idx];
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-11-12 22:43:04 +01:00
|
|
|
change_message_id(old_id, new_id) {
|
2018-05-04 12:44:28 +02:00
|
|
|
// Update our local cache that uses the old id to the new id
|
2020-02-12 06:32:42 +01:00
|
|
|
if (this._hash.has(old_id)) {
|
|
|
|
const msg = this._hash.get(old_id);
|
|
|
|
this._hash.delete(old_id);
|
|
|
|
this._hash.set(new_id, msg);
|
2018-05-04 12:44:28 +02:00
|
|
|
} else {
|
2020-11-12 22:43:04 +01:00
|
|
|
return false;
|
2018-05-04 12:44:28 +02:00
|
|
|
}
|
|
|
|
|
2020-02-12 06:34:41 +01:00
|
|
|
if (this._local_only.has(old_id)) {
|
2018-05-04 12:44:28 +02:00
|
|
|
if (this._is_localonly_id(new_id)) {
|
2020-02-12 06:34:41 +01:00
|
|
|
this._local_only.add(new_id);
|
2018-05-04 12:44:28 +02:00
|
|
|
}
|
2020-02-12 06:34:41 +01:00
|
|
|
this._local_only.delete(old_id);
|
2018-05-04 12:44:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (this._selected_id === old_id) {
|
|
|
|
this._selected_id = new_id;
|
|
|
|
}
|
|
|
|
|
2020-11-12 22:43:04 +01:00
|
|
|
return this.reorder_messages(new_id);
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2020-07-04 20:45:29 +02:00
|
|
|
|
2020-11-12 22:43:04 +01:00
|
|
|
reorder_messages(new_id) {
|
2020-07-23 00:26:13 +02:00
|
|
|
const message_sort_func = (a, b) => a.id - b.id;
|
2018-05-04 12:44:28 +02:00
|
|
|
// If this message is now out of order, re-order and re-render
|
2020-07-23 00:26:13 +02:00
|
|
|
const current_message = this._hash.get(new_id);
|
|
|
|
const index = this._items.indexOf(current_message);
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-23 00:26:13 +02:00
|
|
|
const next = this._next_nonlocal_message(this._items, index, (idx) => idx + 1);
|
|
|
|
const prev = this._next_nonlocal_message(this._items, index, (idx) => idx - 1);
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-15 00:34:28 +02:00
|
|
|
if (
|
|
|
|
(next !== undefined && current_message.id > next.id) ||
|
|
|
|
(prev !== undefined && current_message.id < prev.id)
|
|
|
|
) {
|
2020-07-04 20:45:29 +02:00
|
|
|
blueslip.debug("Changed message ID from server caused out-of-order list, reordering");
|
2020-07-23 00:26:13 +02:00
|
|
|
this._items.sort(message_sort_func);
|
2021-05-07 22:34:38 +02:00
|
|
|
this._all_items.sort(message_sort_func);
|
2020-11-12 22:43:04 +01:00
|
|
|
return true;
|
2020-07-04 20:45:29 +02:00
|
|
|
}
|
2020-11-12 22:43:04 +01:00
|
|
|
|
|
|
|
return false;
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
2018-05-04 12:44:28 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
get_last_message_sent_by_me() {
|
2019-11-02 00:06:25 +01:00
|
|
|
const msg_index = _.findLastIndex(this._items, {sender_id: page_params.user_id});
|
2018-05-04 12:44:28 +02:00
|
|
|
if (msg_index === -1) {
|
2020-09-24 07:50:36 +02:00
|
|
|
return undefined;
|
2018-05-04 12:44:28 +02:00
|
|
|
}
|
2019-11-02 00:06:25 +01:00
|
|
|
const msg = this._items[msg_index];
|
2018-05-04 12:44:28 +02:00
|
|
|
return msg;
|
2020-07-23 00:26:13 +02:00
|
|
|
}
|
|
|
|
}
|